Event loop的化繁為簡(二)

GetFullStack發表於2018-03-23

Node.js的事件迴圈詳細分析

Node.js的事件迴圈詳細分析

為了化簡,我將官方的事件迴圈圖進行了遮擋,只留下重點:timers, poll, check這三個對列。

Node.js Event Loop 的理解 Timers,process.nextTick()

Node.js 事件迴圈

上面這二篇文章對事件迴圈分析的很清楚了。我就只簡要總結。

階段總覽 (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
複製程式碼

執行過程說明

  1. 首先將程式碼全部放到主線列佇列中全部執行。下面程式碼執行完畢。
var fs = require('fs');

function someAsyncOperation (callback) {
  // 花費2毫秒
  fs.readFile(__dirname + '/' + __filename, callback);
}

var timeoutScheduled = Date.now();
var fileReadTime = 0;
複製程式碼
  1. 當執行到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會啟動一個定時執行緒定時,程式繼續。

  1. 在執行到
someAsyncOperation(function () {
  fileReadtime = Date.now();
  while(Date.now() - fileReadtime < 20) {

  }
});
複製程式碼
  1. 發現有
fs.readFile(__dirname + '/' + __filename, callback);
複製程式碼

這裡又會啟動一個檔案讀取執行緒去讀檔案。

  1. 這時程式發現poll裡已經沒有任務,就會阻塞在這裡。
  2. 然後2ms以後,fs.read完成,就將回撥打入poll裡。
  3. 程式將一次性清空poll佇列內容,放入主迴圈佇列,一次執行。再次期間之前的setTimeout到時間,將回撥打入到timers佇列。
  4. 主迴圈佇列執行完畢,檢查check佇列,就清空timers佇列,再放入主迴圈佇列,執行。
  5. 因為有了之前的執行時間消耗,所以等到執行setTimeout回撥的時候,時間就到了22ms。
  6. 程式繼續回到poll,監聽其它動靜。

process.nextTick()

process.nextTick()不在event loop的任何階段執行,而是在各個階段切換的中間執行,即從一個階段切換到下個階段前執行。

階段切換就是指清空佇列以後。

與瀏覽器事件的區別

最大區別就是,瀏覽器的事件迴圈佇列是一個一個的彈出,而Node.js是一次性清空任務佇列。

Javascript Event-loop的化繁為簡(一)

相關文章