nodejs事件迴圈

槑有人用發表於2018-03-24

事件迴圈

Javascript是單執行緒的,但是nodejs通過事件迴圈的方式實現了非阻塞的IO。

Javascript主執行緒通過將發起的IO操作封裝請求物件傳遞給IO執行緒池,主執行緒繼續完成自己接下來的工作。IO執行緒池中利用空閒的執行緒完成IO操作後,會進入事件迴圈階段階段,在此階段取出IO結果和回撥函式進行執行

事件迴圈的階段

事件迴圈階段

每個階段都有一個需要執行的回撥函式的先入先出(FIFO)佇列。同時,每個階段都是特殊的,基本上,當事件迴圈進行到某個階段時,會執行該階段特有的操作,然後執行該階段佇列中的回撥,直到佇列空了或者達到了執行次數限制。這時候,事件迴圈會進入下一個階段,迴圈往復。

階段總覽

  • 計時器(timers):本階段執行setTimeout() 和 setInterval() 計劃的回撥
  • I/O 回撥: 執行幾乎全部發生異常的 close 回撥, 由定時器和setImmediate()計劃的回撥;
  • 空閒,預備(idle,prepare):只內部使用;
  • 輪詢(poll): 獲取新的 I/O 事件;nodejs這時會適當進行阻塞;
  • 檢查(check): 呼叫 setImmediate() 的回撥;
  • close callbacks: 例如 socket.on('close', ... );

階段細節(著重 timer, poll, check)

定時器(timers)

定時器的用途是讓指定的回撥函式在某個閾值後會被執行,具體的執行時間並不一定是那個精確的閾值。定時器的回撥會在制定的時間過後儘快得到執行,然而,作業系統的計劃或者其他回撥的執行可能會延遲該回撥的執行。

注:從技術上來看,輪詢階段控制了定時器的執行時機。

例如,你設定了在100ms後執行某個操作,然後指令碼開始執行一個需要95ms的檔案讀取操作。當檔案讀取完成時,輪詢階段加入了回撥函式,假設回撥函式執行10ms完成,這時poll階段回撥佇列清空完成了,此時才會去執行timer階段回撥。

輪詢(poll)

輪詢階段有兩個主要功能:

  1. 執行已經到時的定時器指令碼
  2. 處理輪詢佇列中的事件。

當事件迴圈進入到輪詢階段卻沒有發現定時器時:

  • 如果輪詢佇列非空,事件迴圈會迭代回撥佇列並同步執行回撥,直到佇列空了或者達到了上限(根據作業系統的不同而設定的上限)
  • 如果輪詢佇列是空的
    • 如果有setImmediate()定義了回撥,那麼事件迴圈會終止輪詢階段並進入檢查階段去執行定時器回撥;
    • 如果沒有setImmediate(),事件回撥會等待回撥被加入佇列並立即執行。

一旦輪詢佇列空了,事件迴圈會查詢已經到時的定時器。如果找到了,事件迴圈就回到定時器階段去執行回撥。

檢查(check)

這個階段允許回撥函式在輪詢階段完成後立即執行。如果輪詢階段空閒了,並且有回撥已經被 setImmediate() 加入佇列,事件迴圈會進入檢查階段而不是在輪詢階段等待。

setImmediate() vs setTimeout()

這兩個很相似,但呼叫時機會的不同會導致它們不同的表現。

  • setImmediate() 被設計成一旦輪詢階段完成就執行回撥函式;
  • setTimeout() 規劃了在某個時間值過後執行回撥函式;

這兩個執行的順序會因為它們被呼叫時的上下文而有所不同。如果都是在主模組呼叫,那麼它們會受到程式效能的影響

但是如果把它們放進 I/O 迴圈中,setImmediate() 的回撥總是先執行,setImmediate() 比 setTimeout() 優勢的地方是 setImmediate() 在 I/O 迴圈中總是先於任何定時器,不管已經定義了多少定時器。

相關文章