JavaScript Event loop 事件循環

Event Loop

本文以 Node.js 爲例,講解 Event Loop 在 Node.js 的實現,原文,JavaScript 中的實現大同小異。javascript

什麼是 Event Loop ?

單線程的 Node.js 可以實現無阻塞IO的緣由就是事件循環(Event Loop)。java

如今大多數系統內核是多線程的,因此它們能夠在後臺執行多個操做,當這些操做完成時,內核就會通知 Node.js,而這些操做的回調函數被添加到事件輪詢列表(poll queue),而且 Node.js 會在適當的時機執行回調函數。node

概覽 Event Loop

當 Node.js 開始執行時,便初始化 Event Loop,執行過程當中會存在許多異步操做,如:REPL、定時器(timers)、調用異步 API(請求,事件監聽),在主進程代碼執行完後,便開始運行 Event Loopgit

下圖描述了 Event Loop 中的各個階段github

┌───────────────────────┐
┌─>│        timers         │ 這個階段執行 `setTimeout()` 和 `setInterval()` 中的回調函數
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     I/O callbacks     │ 這個階段執行除了 `close` 回調函數之外的幾乎全部的 I/0 回調函數
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     idle, prepare     │ 這個階段僅僅 Node.js 內部使用
│  └──────────┬────────────┘      ┌───────────────┐
│  ┌──────────┴────────────┐      │   incoming:   │
│  │         poll          │<─────┤  connections, │ 執行隊列中的回調函數、檢索新的回調函數
│  └──────────┬────────────┘      │   data, etc.  │
│  ┌──────────┴────────────┐      └───────────────┘
│  │        check          │ `setImmediate()` 將在這裏被調用
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
└──┤    close callbacks    │ `close` 回調函數被調用如:socket.on('close', ...)
   └───────────────────────┘

詳解 Event Loop 的各個階段

timers

setTimeout() 和 setInterval() 都要指定一個運行時間,這個運行時間其實不是確切的運行時間,而是一個指望時間,Event Loop 會在 timers 階段執行超過時望時間的定時器回調函數,但因爲你不肯定在其餘階段甚至主進程中的事件執行時間,因此定時器不必定會按時執行。多線程

var asyncApi = function (callback) {
  setTimeout(callback, 90)
}

const timeoutScheduled = Date.now();
setTimeout(() => {
  const delay = Date.now() - timeoutScheduled;
  console.log(`${delay}ms setTimeout 被執行`); // 140ms 以後被執行
}, 100);

asyncApi(() => {
  const startCallback = Date.now();
  while (Date.now() - startCallback < 50) {
    // do nothing
  }
})
I/O callbacks

這個階段主要執行一些系統操做帶來的回調函數,如 TCP 錯誤,若是 TCP 嘗試連接時出現 ECONNREFUSED 錯誤 ,一些 *nix 會把這個錯誤報告給 Node.js。而這個錯誤報告會先進入隊列中,而後在 I/O callbacks 階段執行。異步

poll

poll 階段有兩個主要功能:socket

  1. 也會執行時間定時器到達指望時間的回調函數async

  2. 執行事件循環列表(poll queue)裏的函數ide

當 Event Loop 進入 poll 階段而且沒有其他的定時器,那麼:

  • 若是事件循環列表不爲空,則迭代同步的執行隊列中的函數。

  • 若是事件循環列表爲空,則判斷是否有 setImmediate() 函數待執行。若是有結束 poll 階段,直接到

check 階段。若是沒有,則等待回調函數進入隊列並當即執行。

check

在 poll 階段結束以後,執行 setImmediate()

close

忽然結束的事件的回調函數會在這裏觸發,若是 socket.destroy(),那麼 close 會被觸發在這個階段,也有可能經過 process.nextTick() 來觸發。

setImmediate()、setTimeout()、process.nextTick()

這裏要說明一下 process.nextTick() 是在下次事件循環以前運行,若是把 process.nextTick()setImmediate() 寫在一塊兒,那麼是 process.nextTick() 先執行。nextimmediate 快,官方也說這個函數命名有問題,可是由於歷史存留沒辦法解決。

process.nextTick(() => {
  console.log('nextTick');
});
setImmediate(() => {
  console.log('setImmediate');
});
setTimeout(() => {
  console.log('setTimeout'); 
}, 0)

// 執行結果,nextTick, setTimeout, setImmediate
// 查看 Node.js 源碼,setTimeout(fun, 0) 會轉化成 setTimeout(fun, 1),因此在這種簡單的狀況下,對於不一樣設備,setImmediate 有可能早於 setTimeout 執行。

總結

理解事件循環,會知道 JavaScript 如何無阻塞運行的,以及它簡潔的開發思路和事件驅動風格。


做者:肖沐宸,github

相關文章
相關標籤/搜索