上一篇文章提到了單執行緒非同步執行機制的好處,在於可以善用個別執行緒的資源,不在耗費時間的 IO 請求上浪費時間等待。
而幫助 Node 做到這件事的就是 Event Loop 這個機制!
Call stack 是 Node 用來記錄目前要執行什麼函式的一個資料結構,是個 LIFO(後進先出) 的構造。
而 event loop 會不斷輪詢查看裡面是否有需要執行的 function,只要有,就會優先去執行。
這部分官網文件圖解特別清楚,所以在這邊不另外畫圖詳解,請點進去看一下流程圖,會很清楚他的概念:
Call Stack
┌───────────────────────────┐
┌─────>│ NextTick Queue │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ MicroTask Queue │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ timers │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
┌───────────────────────────┐ │ │ pending callbacks │
│ Call Stack │<──────┤ └─────────────┬─────────────┘
└───────────────────────────┘ │ ┌─────────────┴─────────────┐
│ │ idle, prepare │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ poll │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ check │
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
└──────┤ close callbacks │
└───────────────────────────┘
當 Call stack 為空時,event loop 會去檢查 event queue,將裡面的任務放進 call stack 內做執行。
而 event queue 其實是由很多個 queue 所組成的,而 queue 是一種 LILO(後進後出)的構造。而這些 queue 內正是放著 node 的非同步的任務們。
event queue 官網有詳解且有圖示可參考:
Event Loop
大概歸納一下,Event Loop 在執行時的執行優先序大概是這樣:
if (Call Stack 非空) {
// 執行 Call Stack pop 出的內容
} else if (NextTick Queue 非空) { // process.nextTick 傳的 callback 會被放進這個 queue
// 取出 NextTick Queue 任務進 call stack
} else if (MicroTask Queue 非空) { // promise 非同步要執行的 callback 會被放進這邊
// 取出 MicroTask Queue 任務進 call stack
} else if (Timers 非空) { // setTimeout 或 setInterval 的計時觸發時,callback 會被放進這邊
// 取出 Timers 任務進 call stack
} else if (Pending Callback 非空) { // 為某些系統操作(例如 TCP 錯誤)執行的 callback
// 取出 Pending Callback 任務進 call stack
} else if (Idle, Prepare 非空) { // node 內部使用,可忽略
// 取出 Idle, Prepare 任務進 call stack
} else if (Polling 非空) {
// 取出 Polling 任務進 call stack
} else if (Check 非空) { // setImmediate 的 callback 會被放進這邊
// 取出 Check 任務進 call stack
} else if (Close Callback 非空) { // 處理連線或是 handle 關閉的 callback (例如 socket.destroy())
// 取出 Close Callback 任務進 call stack
}
如果上面不太理解的,可以參考這篇,裡面有程式範例可以比對,該筆者鐵人賽還有出書,我覺得也寫的蠻不錯的。
全端工程師生存筆記
還有這個影片也可以參考:
The Complete Node js: The Node js Event Loop
node main.js
時,event loop 會先依序去執行 call stack 的內容 (main.js 的程式會被依行並且根據函式執行的位置被放進去,執行完的內容則會被 pop 出 stack)透過 event loop,node 在 單執行緒上不用再擔心阻塞問題,並且可以有效率的同時處理許多 IO 請求。
這不是跟魔法一樣嗎?
明天我們將繼續了解 node 如何在單執行緒的限制下,善用多核 CPU 的優勢。