儘管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
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的等待,沒有就關閉
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 也有一個最大值(基於系統),會在超過最大值以前中止輪詢更多的事件。
爲系統操做(好比tcp錯誤類型)執行回調函數
當tcp socket嘗試鏈接時接收到ECONNREFUSED,類unix系統將會想報道錯誤,要會在這個phase排隊執行。
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的回調函數
特別的 timer
在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
參考: