搜尋
首頁web前端js教程解析Node.js的事件循環機制

解析Node.js的事件循環機制

Oct 20, 2018 pm 04:25 PM
es6javascriptnode.js

這篇文章帶給大家的內容是關於解析Node.js的事件循環機制,有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。

在瀏覽器篇已經對事件循環機制和一些相關的概念作了詳細介紹,但主要是針對瀏覽器端的研究,Node環境是否也一樣呢?先看一個demo:

setTimeout(()=>{
    console.log('timer1')
    Promise.resolve().then(function() {
        console.log('promise1')
    })}, 0)setTimeout(()=>{
    console.log('timer2')
    Promise.resolve().then(function() {
        console.log('promise2')
    })}, 0)

肉眼編譯運行一下,蒽,在瀏覽器的結果就是下面這個了,道理都懂,就不累述了。

timer1
promise1
timer2
promise2

那麼Node下執行看看,咦。 。 。奇怪,跟瀏覽器的運作結果不一樣~

timer1
timer2
promise1
promise2

範例說明,瀏覽器和Node.js 的事件循環機制是有差別的,一起來看個究竟吧~

Node.js的事件處理

Node.js採用V8作為js的解析引擎,而I/O處理方面使用了自己設計的libuv,libuv是基於事件驅動的跨平台抽象層,封裝了不同作業系統一些底層特性,對外提供統一的API,事件循環機制也是它裡面的實現,核心原始碼參考:

int uv_run(uv_loop_t* loop, uv_run_mode mode) {
  int timeout;
  int r;
  int ran_pending;
  r = uv__loop_alive(loop);
  if (!r)
    uv__update_time(loop);
  while (r != 0 && loop->stop_flag == 0) {
    uv__update_time(loop);
    // timers阶段
    uv__run_timers(loop);
    // I/O callbacks阶段
    ran_pending = uv__run_pending(loop);
    // idle阶段
    uv__run_idle(loop);
    // prepare阶段
    uv__run_prepare(loop);
    timeout = 0;
    if ((mode == UV_RUN_ONCE && !ran_pending) || mode == UV_RUN_DEFAULT)
      timeout = uv_backend_timeout(loop);
    // poll阶段
    uv__io_poll(loop, timeout);
    // check阶段
    uv__run_check(loop);
    // close callbacks阶段
    uv__run_closing_handles(loop);
    if (mode == UV_RUN_ONCE) {
      uv__update_time(loop);
      uv__run_timers(loop);
    }
    r = uv__loop_alive(loop);
    if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT)
      break;
  }
  if (loop->stop_flag != 0)
    loop->stop_flag = 0;
  return r;
}

根據Node.js官方介紹,每次事件循環都包含了6個階段,對應libuv 原始碼中的實現,如下圖所示

解析Node.js的事件循環機制

timers 階段:這個階段執行timer(setTimeout、setInterval)的回呼

#I/ O callbacks 階段:執行一些系統呼叫錯誤,例如網路通訊的錯誤回呼

idle, prepare 階段:僅node內部使用

poll 階段:取得新的I/O事件, 適當的條件下node將阻塞在這裡

check 階段:執行setImmediate() 的回呼

close callbacks 階段:執行socket 的close 事件回呼

我們重點看timers、poll 、check這3個階段就好,因為日常開發的絕大部分非同步任務都是在這3個階段處理的。

timers 階段

timers 是事件循環的第一個階段,Node 會去檢查有無已過期的timer,如果有則把它的回調壓入timer的任務隊列中等待執行,事實上,Node 並不能保證timer在預設時間到了就會立即執行,因為Node對timer的過期檢查不一定可靠,它會受機器上其它運行程序影響,或者那個時間點主線程不空閒。例如下面的程式碼,setTimeout() 和 setImmediate() 的執行順序是不確定的。

setTimeout(() => {
  console.log('timeout')
  }, 0)
  setImmediate(() => {
  console.log('immediate')
  })

但把它們放到一個I/O回呼裡面,就一定是 setImmediate() 先執行,因為poll階段後面就是check階段。

poll 階段

poll 階段主要有2個功能:

#處理poll 佇列的事件

當有已逾時的timer,執行它的回調函數

even loop將同步執行poll佇列裡的回調,直到佇列為空或執行的回呼達到系統上限(上限具體多少未詳),接下來even loop會去檢查有無預設的setImmediate( ),分兩種情況:

若有預設的setImmediate(), event loop將結束poll階段進入check階段,並執行check階段的任務隊列

若沒有預設的setImmediate(),event loop將阻塞在該階段等待

注意一個細節,沒有setImmediate()會導致event loop阻塞在poll階段,這樣之前設定的timer豈不是執行不了了?所以咧,在poll階段event loop會有檢查機制,檢查timer隊列是否為空,如果timer隊列非空,event loop就開始下一輪事件循環,也就是重新進入timer階段。

check 階段

setImmediate()的回呼會被加入check佇列中, 從event loop的階段圖可以知道,check階段的執行順序在poll階段之後。

小結

event loop 的每個階段都有一個任務隊列

當event loop 到達某個階段時,將執行該階段的任務隊列,直到隊列清空或執行的回呼達到系統上限後,才會轉入下一個階段

當所有階段被順序執行一次後,稱event loop 完成了一個tick

講得好有道理,可是沒有demo我還是懂不全啊,憋急,now!

const fs = require('fs')fs.readFile('test.txt', () => {
  console.log('readFile')
  setTimeout(() => {
    console.log('timeout')
  }, 0)
  setImmediate(() => {
    console.log('immediate')
  })
  })

執行結果應該都沒有疑問了

readFile
immediate
timeout

Node.js 與瀏覽器的Event Loop 差異

回顧上一篇,瀏覽器環境下,microtask的任務佇列是每個macrotask執行完之後執行。

解析Node.js的事件循環機制

而在Node.js中,microtask會在事件循環的各個階段之間執行,也就是一個階段執行完畢,就會去執行microtask佇列的任務。

解析Node.js的事件循環機制

demo回顧

回顧文章最開始的demo,全域腳本(main())執行,將2個timer依次放入timer佇列,main()執行完畢,呼叫堆疊空閒,任務佇列開始執行;

解析Node.js的事件循環機制

首先进入timers阶段,执行timer1的回调函数,打印timer1,并将promise1.then回调放入microtask队列,同样的步骤执行timer2,打印timer2;

至此,timer阶段执行结束,event loop进入下一个阶段之前,执行microtask队列的所有任务,依次打印promise1、promise2。

对比浏览器端的处理过程:

解析Node.js的事件循環機制

process.nextTick() VS setImmediate()

In essence, the names should be swapped. process.nextTick() fires more immediately than setImmediate()

来自官方文档有意思的一句话,从语义角度看,setImmediate() 应该比 process.nextTick() 先执行才对,而事实相反,命名是历史原因也很难再变。

process.nextTick() 会在各个事件阶段之间执行,一旦执行,要直到nextTick队列被清空,才会进入到下一个事件阶段,所以如果递归调用 process.nextTick(),会导致出现I/O starving(饥饿)的问题,比如下面例子的readFile已经完成,但它的回调一直无法执行:

const fs = require('fs')const starttime = Date.now()let endtime
fs.readFile('text.txt', () => {
  endtime = Date.now()
  console.log('finish reading time: ', endtime - starttime)})let index = 0function handler () {
  if (index++ >= 1000) return
  console.log(`nextTick ${index}`)
  process.nextTick(handler)
  // console.log(`setImmediate ${index}`)
  // setImmediate(handler)}handler()

process.nextTick()的运行结果:

nextTick 1
nextTick 2
......
nextTick 999
nextTick 1000
finish reading time: 170

替换成setImmediate(),运行结果:

setImmediate 1
setImmediate 2
finish reading time: 80
......
setImmediate 999
setImmediate 1000

这是因为嵌套调用的 setImmediate() 回调,被排到了下一次event loop才执行,所以不会出现阻塞。

总结

1、Node.js 的事件循环分为6个阶段

2、浏览器和Node 环境下,microtask 任务队列的执行时机不同

Node.js中,microtask 在事件循环的各个阶段之间执行

浏览器端,microtask 在事件循环的 macrotask 执行完之后执行

3、递归的调用process.nextTick()会导致I/O starving,官方推荐使用setImmediate()

以上是解析Node.js的事件循環機制的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文轉載於:github。如有侵權,請聯絡admin@php.cn刪除
Python vs. JavaScript:學習曲線和易用性Python vs. JavaScript:學習曲線和易用性Apr 16, 2025 am 12:12 AM

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

Python vs. JavaScript:社區,圖書館和資源Python vs. JavaScript:社區,圖書館和資源Apr 15, 2025 am 12:16 AM

Python和JavaScript在社區、庫和資源方面的對比各有優劣。 1)Python社區友好,適合初學者,但前端開發資源不如JavaScript豐富。 2)Python在數據科學和機器學習庫方面強大,JavaScript則在前端開發庫和框架上更勝一籌。 3)兩者的學習資源都豐富,但Python適合從官方文檔開始,JavaScript則以MDNWebDocs為佳。選擇應基於項目需求和個人興趣。

從C/C到JavaScript:所有工作方式從C/C到JavaScript:所有工作方式Apr 14, 2025 am 12:05 AM

從C/C 轉向JavaScript需要適應動態類型、垃圾回收和異步編程等特點。 1)C/C 是靜態類型語言,需手動管理內存,而JavaScript是動態類型,垃圾回收自動處理。 2)C/C 需編譯成機器碼,JavaScript則為解釋型語言。 3)JavaScript引入閉包、原型鍊和Promise等概念,增強了靈活性和異步編程能力。

JavaScript引擎:比較實施JavaScript引擎:比較實施Apr 13, 2025 am 12:05 AM

不同JavaScript引擎在解析和執行JavaScript代碼時,效果會有所不同,因為每個引擎的實現原理和優化策略各有差異。 1.詞法分析:將源碼轉換為詞法單元。 2.語法分析:生成抽象語法樹。 3.優化和編譯:通過JIT編譯器生成機器碼。 4.執行:運行機器碼。 V8引擎通過即時編譯和隱藏類優化,SpiderMonkey使用類型推斷系統,導致在相同代碼上的性能表現不同。

超越瀏覽器:現實世界中的JavaScript超越瀏覽器:現實世界中的JavaScriptApr 12, 2025 am 12:06 AM

JavaScript在現實世界中的應用包括服務器端編程、移動應用開發和物聯網控制:1.通過Node.js實現服務器端編程,適用於高並發請求處理。 2.通過ReactNative進行移動應用開發,支持跨平台部署。 3.通過Johnny-Five庫用於物聯網設備控制,適用於硬件交互。

使用Next.js(後端集成)構建多租戶SaaS應用程序使用Next.js(後端集成)構建多租戶SaaS應用程序Apr 11, 2025 am 08:23 AM

我使用您的日常技術工具構建了功能性的多租戶SaaS應用程序(一個Edtech應用程序),您可以做同樣的事情。 首先,什麼是多租戶SaaS應用程序? 多租戶SaaS應用程序可讓您從唱歌中為多個客戶提供服務

如何使用Next.js(前端集成)構建多租戶SaaS應用程序如何使用Next.js(前端集成)構建多租戶SaaS應用程序Apr 11, 2025 am 08:22 AM

本文展示了與許可證確保的後端的前端集成,並使用Next.js構建功能性Edtech SaaS應用程序。 前端獲取用戶權限以控制UI的可見性並確保API要求遵守角色庫

JavaScript:探索網絡語言的多功能性JavaScript:探索網絡語言的多功能性Apr 11, 2025 am 12:01 AM

JavaScript是現代Web開發的核心語言,因其多樣性和靈活性而廣泛應用。 1)前端開發:通過DOM操作和現代框架(如React、Vue.js、Angular)構建動態網頁和單頁面應用。 2)服務器端開發:Node.js利用非阻塞I/O模型處理高並發和實時應用。 3)移動和桌面應用開發:通過ReactNative和Electron實現跨平台開發,提高開發效率。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
4 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
4 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
4 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.聊天命令以及如何使用它們
4 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

mPDF

mPDF

mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器

EditPlus 中文破解版

EditPlus 中文破解版

體積小,語法高亮,不支援程式碼提示功能

PhpStorm Mac 版本

PhpStorm Mac 版本

最新(2018.2.1 )專業的PHP整合開發工具

WebStorm Mac版

WebStorm Mac版

好用的JavaScript開發工具