node的事件機制

什麼是事件循環(event loop)?

儘管js是單線程的,事件循環機制,經過在合適的時候把操做交給系統內核,從而容許node執行非阻塞的io操做
當操做完成時,內核告知node.js,合適的回調函數會被加入輪詢隊列,最終被執行。
Node.js啓動的時候,初始化event loop,處理提供的腳本,腳本中可能調用異步API,調度timers,或者調用process.nextTick(),而後處理event loopnode

下圖是簡化的事件循環操做順序圖overview異步

┌───────────────────────┐
┌─>│        timers         │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     I/O callbacks     │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
│  │     idle, prepare     │
│  └──────────┬────────────┘      ┌───────────────┐
│  ┌──────────┴────────────┐      │   incoming:   │
│  │         poll          │<─────┤  connections, │
│  └──────────┬────────────┘      │   data, etc.  │
│  ┌──────────┴────────────┐      └───────────────┘
│  │        check          │
│  └──────────┬────────────┘
│  ┌──────────┴────────────┐
└──┤    close callbacks    │
      └───────────────────────┘

圖中每一個box就是一個phase,每一個phase有一個先進先出的回調函數的隊列,
event loop進入了一個phase,就會執行phase中全部的操做,而後執行回調函數,直到隊列耗盡了,或者回調函數執行數量到達最大數,接下來就去下一個phasesocket

由於任何一個操做均可能調度更多的操做,並且poll phase中新的事件由內核排隊,因此正在輪詢的事件在被處理的時候,poll事件們可能會排隊。
結果:長時間的運行回調函數容許poll phase運行事件比timer的閾值更長。tcp

phase overview 階段概況

  • timers:執行由setTimeout() and setInterval()調度的回調函數ide

  • I/O callbacks:執行全部的回調函數,除了 close callbacks(由timers,setImmediate()調度)函數

  • idle, prepare:內部使用oop

  • poll:獲取新的io事件,當合適的時候,node會阻塞在這裏性能

  • check: setImmediate()回調函數會在這裏調用ui

  • close callbacks: e.g. socket.on('close', ...)this

每次運行event loop,node檢查是否有對任何異步io或者timers的等待,沒有就關閉

Phases in Detail(各階段細述)

timers

timers指定閾值(threshold)以後,會執行回調函數,但threshold不是執行回調函數的確切時間(只是最短期)。
timers回調函數一旦能夠執行了就會被執行。然而操做系統的調度或者其餘的回調函數可能推遲它的執行。
由poll phase來控制何時timers被執行

var fs = require('fs');
function someAsyncOperation (callback) {
  // Assume this takes 95ms to complete
  fs.readFile('/path/to/file', callback);
}
var timeoutScheduled = Date.now();
setTimeout(function () {
  var delay = Date.now() - timeoutScheduled;
  console.log(delay + "ms have passed since I was scheduled");
}, 100);
// do someAsyncOperation which takes 95 ms to complete
someAsyncOperation(function () {
  var startCallback = Date.now();
  // do something that will take 10ms...
  while (Date.now() - startCallback < 10) {
    ; // do nothing
  }
});

一開始timer被調度,裏面的回調函數執行log。
而後事件循環進入poll phase,此時隊列是空的(由於fs.readFile()沒有完成),因此就會等着,直到最先的timer的閾值(100)到時間,等了95 ms(還沒到,畢竟定的是100),fs.readFile() 這個時候完成了,因此它的回調函數就回被加poll的隊列而且被執行(執行10s),當回調函數完成了,隊列又空了,因此,event loop將會看到timer的閾值(100)已經到了,
而後回到timers這個phase去執行timers的回調函數,也就是,打印出105秒

爲了防止poll phase 獨佔耗盡 event loop,libuv 也有一個最大值(基於系統),會在超過最大值以前中止輪詢更多的事件。

I/O callbacks

爲系統操做(好比tcp錯誤類型)執行回調函數
當tcp socket嘗試鏈接時接收到ECONNREFUSED,類unix系統將會想報道錯誤,要會在這個phase排隊執行。

poll

poll phase有兩個功能

  • 爲到了時間的timers執行腳本,而後

  • 處理poll隊列的事件

當event loop 進入poll phase且沒有timers被調度,下面的事情會發生

  • poll不空,

    • 經過回調函數隊列迭代的執行

  • poll棧是空的

    • 若是腳本已經被setImmediate()調度,事件循環將會終止poll phase,到check phase去執行那些被調度的腳本

    • 等着回調函數被加進隊列,而後立馬執行它
      一旦poll空了,event loop將回檢查timers有沒有thresholds到了,有的話,wrap back to the timers phase,而後執行timers的回調函數

check

特別的 timer

close callbacks

setImmediate and setTimeout()

  • 在poll完成之後執行

  • 在最小事件以後執行

執行順序:
依賴於調用的上下文

  • 若是都在main module ,事件會被進程的性能限制(被其餘應用影響)

    • not within an I/O cycle:不肯定的

    • within an I/O cycle:immediate老是先(更好)

// timeout_vs_immediate.js
setTimeout(function timeout () {
  console.log('timeout');
},0);

setImmediate(function immediate () {
  console.log('immediate');
});
// timeout_vs_immediate.js
var fs = require('fs')

fs.readFile(__filename, () => {
  setTimeout(() => {
    console.log('timeout')
  }, 0)
  setImmediate(() => {
    console.log('immediate')
  })
})

The Node.js Event Loop, Timers

參考:

相關文章
相關標籤/搜索