Node.js的事件迴圈詳細分析
為了化簡,我將官方的事件迴圈圖進行了遮擋,只留下重點:timers, poll, check這三個對列。
Node.js Event Loop 的理解 Timers,process.nextTick()
上面這二篇文章對事件迴圈分析的很清楚了。我就只簡要總結。
階段總覽 (Phases Overview)
- 計時器(timers):執行 setTimeout() 和 setInterval() 的回撥,只要時間一到,就把回撥放進去。
- I/O 回撥: 執行幾乎全部發生異常的 close 回撥, 由定時器和setImmediate()計劃的回撥;
- 空閒,預備(idle,prepare):只內部使用;
- 輪詢(poll): 獲取新的 I/O 事件;nodejs這時會適當進行阻塞;
- 檢查(check): 執行 setImmediate() 的回撥;
- close callbacks: 例如 socket.on('close', ... );
var fs = require('fs');
function someAsyncOperation (callback) {
// 花費2毫秒
fs.readFile(__dirname + '/' + __filename, callback);
}
var timeoutScheduled = Date.now();
var fileReadTime = 0;
setTimeout(function () {
var delay = Date.now() - timeoutScheduled;
console.log('setTimeout: ' + (delay) + "ms have passed since I was scheduled");
console.log('fileReaderTime',fileReadtime - timeoutScheduled);
}, 10);
someAsyncOperation(function () {
fileReadtime = Date.now();
while(Date.now() - fileReadtime < 20) {
}
});
複製程式碼
// 執行結果
setTimeout: 22ms have passed since I was scheduled
fileReaderTime 2
複製程式碼
執行過程說明
- 首先將程式碼全部放到主線列佇列中全部執行。下面程式碼執行完畢。
var fs = require('fs');
function someAsyncOperation (callback) {
// 花費2毫秒
fs.readFile(__dirname + '/' + __filename, callback);
}
var timeoutScheduled = Date.now();
var fileReadTime = 0;
複製程式碼
- 當執行到setTimeout時,
setTimeout(function () {
var delay = Date.now() - timeoutScheduled;
console.log('setTimeout: ' + (delay) + "ms have passed since I was scheduled");
console.log('fileReaderTime',fileReadtime - timeoutScheduled);
}, 10);
複製程式碼
node.js會啟動一個定時執行緒定時,程式繼續。
- 在執行到
someAsyncOperation(function () {
fileReadtime = Date.now();
while(Date.now() - fileReadtime < 20) {
}
});
複製程式碼
- 發現有
fs.readFile(__dirname + '/' + __filename, callback);
複製程式碼
這裡又會啟動一個檔案讀取執行緒去讀檔案。
- 這時程式發現poll裡已經沒有任務,就會阻塞在這裡。
- 然後2ms以後,fs.read完成,就將回撥打入poll裡。
- 程式將一次性清空poll佇列內容,放入主迴圈佇列,一次執行。再次期間之前的setTimeout到時間,將回撥打入到timers佇列。
- 主迴圈佇列執行完畢,檢查check佇列,就清空timers佇列,再放入主迴圈佇列,執行。
- 因為有了之前的執行時間消耗,所以等到執行setTimeout回撥的時候,時間就到了22ms。
- 程式繼續回到poll,監聽其它動靜。
process.nextTick()
process.nextTick()不在event loop的任何階段執行,而是在各個階段切換的中間執行,即從一個階段切換到下個階段前執行。
階段切換就是指清空佇列以後。
與瀏覽器事件的區別
最大區別就是,瀏覽器的事件迴圈佇列是一個一個的彈出,而Node.js是一次性清空任務佇列。