簡單-Node.js Event Loop 的理解 Timers镜会,process.nextTick()

當(dāng)Node.js啟動時會初始化event loop, 每一個event loop都會包含按如下順序六個循環(huán)階段,

  • timer ( 這個階段執(zhí)行setTimeout(callback) and setInterval(callback)預(yù)定的callback;)
  • I/O callbacks(執(zhí)行除了 close事件的callbacks、被timers(定時器,setTimeout添祸、setInterval等)設(shè)定的callbacks、setImmediate()設(shè)定的callbacks之外的callbacks;)
  • idle,prepare 僅node內(nèi)部使用;
  • poll() 獲取新的I/O事件, 適當(dāng)?shù)臈l件下node將阻塞在這里;
    • incomming
    • connections
    • data ,etc
  • check執(zhí)行setImmediate() 設(shè)定的callbacks;
  • close callbacks 比如socket.on(‘close’, callback)的callback會在這個階段執(zhí)行.
    每一個階段都有一個裝有callbackDe FIFO queue(隊列)寻仗,當(dāng)event loop運行到一個指定的階段時刃泌,node將執(zhí)行該階段的隊列,當(dāng)隊列callback執(zhí)行完或者執(zhí)行callbacks數(shù)量超過該階段的上限時署尤,event loop會轉(zhuǎn)入下一下階段.
    注意上面六個階段都不包括 process.nextTick()
    poll階段
    poll階段是銜接整個event loop各個階段比較重要的階段耙替,為了便于后續(xù)例子的理解,本文和原文的介紹順序不一樣曹体,本文先講這個階段俗扇;
    在node.js里,任何異步方法(除timer,close,setImmediate之外)完成時箕别,都會將其callback加到poll queue里,并立即執(zhí)行狐援。

poll 階段有兩個主要的功能
1 處理poll隊列(poll quenue)的事件(callback);
2 執(zhí)行timers的callback,當(dāng)?shù)竭_timers指定的時間時;

如果event loop進入了 poll階段,且代碼未設(shè)定timer究孕,將會發(fā)生下面情況:

  • 如果poll queue不為空,event loop將同步的執(zhí)行queue里的callback,直至queue為空爹凹,或執(zhí)行的callback到達系統(tǒng)上限;

  • 如果poll queue為空厨诸,將會發(fā)生下面情況:

    • 如果代碼已經(jīng)被setImmediate()設(shè)定了callback, event loop將結(jié)束poll階段進入check階段,并執(zhí)行check階段的queue (check階段的queue是 setImmediate設(shè)定的)
    • 如果代碼沒有設(shè)定setImmediate(callback)禾酱,event loop將阻塞在該階段等待callbacks加入poll queue;

如果event loop進入了 poll階段微酬,且代碼設(shè)定了timer:

  • 如果poll queue進入空狀態(tài)時(即poll 階段為空閑狀態(tài)),event loop將檢查timers,如果有1個或多個timers時間時間已經(jīng)到達颤陶,event loop將按循環(huán)順序進入 timers 階段颗管,并執(zhí)行timer queue.

以上便是整個event loop時間循環(huán)的各個階段運行機制,有了這層理解滓走,我們來看幾個例子
注意垦江,例子中給出的時間在不同機器下和同一機器下不同執(zhí)行時刻,其值都會有差異
example 1

解釋:
當(dāng)時程序啟動時搅方,event loop初始化:

1 timer階段(無callback到達比吭,setTimeout需要10毫秒)
2 i/o callback階段,無異步i/o完成
3 忽略
4 poll階段姨涡,阻塞在這里衩藤,當(dāng)運行2ms時,fs.readFile完成涛漂,將其callback加入 poll隊列赏表,并執(zhí)行callback, 其中callback要消耗20毫秒,等callback之行完,poll處于空閑狀態(tài)瓢剿,由于之前設(shè)定了timer逢慌,因此檢查timers,發(fā)現(xiàn)timer設(shè)定時間是20ms,當(dāng)前時間運行超過了該值跋选,因此涕癣,立即循環(huán)回到timer階段執(zhí)行其callback,因此,雖然setTimeout的20毫秒前标,但實際是22毫秒后執(zhí)行坠韩。

example 2


解釋:
當(dāng)時程序啟動時,event loop初始化:

1 timer階段(無callback到達炼列,setTimeout需要10毫秒)
2 i/o callback階段只搁,無異步i/o完成
3 忽略
4 poll階段,阻塞在這里俭尖,當(dāng)運行5ms時氢惋,poll依然空閑,但已設(shè)定timer,且時間已到達稽犁,因此焰望,event loop需要循環(huán)到timer階段,執(zhí)行setTimeout callback,由于從poll --> timer中間要經(jīng)歷check,close階段,這些階段也會消耗一定時間,因此執(zhí)行setTimeout callback實際是7毫秒 然后又回到poll階段等待異步i/o完成已亥,在9毫秒時fs.readFile完成熊赖,其callback加入poll queue并執(zhí)行。

setTimeout 和 setImmediate
二者非常相似虑椎,但是二者區(qū)別取決于他們什么時候被調(diào)用.

  • setImmediate 設(shè)計在poll階段完成時執(zhí)行震鹉,即check階段;
  • setTimeout 設(shè)計在poll階段為空閑時捆姜,且設(shè)定時間到達后執(zhí)行传趾;但其在timer階段執(zhí)行

其二者的調(diào)用順序取決于當(dāng)前event loop的上下文,如果他們在異步i/o callback之外調(diào)用泥技,其執(zhí)行先后順序是不確定的

setTimeout(function timeout () { console.log('timeout');},0);
setImmediate(function immediate () { console.log('immediate');});

$ node timeout_vs_immediate.js
timeout
immediate

$ node timeout_vs_immediate.js
immediate
timeout

關(guān)于這點的原因
在node中浆兰,setTimeout(cb, 0) === setTimeout(cb, 1);
而setImmediately屬于uv_run_check的部分
確實每次loop進來,都是先檢查uv_run_timer的零抬,但是由于cpu工作耗費時間镊讼,比如第一次獲取的hrtime為0
那么setTimeout(cb, 1),超時時間就是loop->time = 1(ms平夜,node定時器精確到1ms蝶棋,但是hrtime是精確到納秒級別的)
所以第一次loop進來的時候就有兩種情況:
1.由于第一次loop前的準(zhǔn)備耗時超過1ms,當(dāng)前的loop->time >=1 忽妒,則uv_run_timer生效玩裙,timeout先執(zhí)行
2.由于第一次loop前的準(zhǔn)備耗時小于1ms兼贸,當(dāng)前的loop->time = 0,則本次loop中的第一次uv_run_timer不生效吃溅,那么io_poll后先執(zhí)行uv_run_check溶诞,即immediate先執(zhí)行,然后等close cb執(zhí)行完后决侈,繼續(xù)執(zhí)行uv_run_timer

那么你說的為什么在回調(diào)中螺垢,一定是先immediate執(zhí)行呢,其實也很容易理解你可以思考一下你寫的場景
由于你的timeout和immediate的事件注冊是在readFile的回調(diào)執(zhí)行時赖歌,觸發(fā)的所以必然的枉圃,在readFile的回調(diào)執(zhí)行前的每一次event loop進來的uv_run_timer都不會有超時事件觸發(fā)
那么當(dāng)readFile執(zhí)行完畢,kevent收到監(jiān)聽的fd事件完成后庐冯,執(zhí)行了該回調(diào)孽亲,此時
1.timeout事件注冊
2.immediate事件注冊
3.由于readFile的回調(diào)執(zhí)行完畢,那么就會從uv_io_poll中出來展父,此時立即執(zhí)行uv_run_check返劲,所以immediate事件被執(zhí)行掉
4.最后的uv_run_timer檢查timeout事件,執(zhí)行timeout事件

所以你會發(fā)現(xiàn)栖茉,在I/O回調(diào)中注冊的兩者篮绿,永遠都是immediately先執(zhí)行

但當(dāng)二者在異步i/o callback內(nèi)部調(diào)用時,總是先執(zhí)行setImmediate吕漂,再執(zhí)行setTimeout

var fs = require('fs')fs.readFile(__filename, () => 
{ setTimeout(() => {
 console.log('timeout')
 }, 0) 
setImmediate(() => { 
console.log('immediate') 
})})
$ node timeout_vs_immediate.js
immediate
timeout

理解了event loop的各階段順序這個例子很好理解:因為fs.readFile callback執(zhí)行完后搔耕,程序設(shè)定了timer 和 setImmediate,因此poll階段不會被阻塞進而進入check階段先執(zhí)行setImmediate痰娱,后進入timer階段執(zhí)行setTimeout

process.nextTick()###

process.nextTick()不在event loop的任何階段執(zhí)行,而是在各個階段切換的中間執(zhí)行,即從一個階段切換到下個階段前執(zhí)行菩收。
![]6@4Y@3JMH(~95A`1.png](http://upload-images.jianshu.io/upload_images/1058258-4edc9f71a2b32cda.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
從poll —> check階段梨睁,先執(zhí)行process.nextTick,
nextTick1
nextTick2
然后進入check,setImmediate娜饵,
setImmediate
執(zhí)行完setImmediate后坡贺,出check,進入close callback前,執(zhí)行process.nextTick
nextTick3
最后進入timer執(zhí)行setTimeout
setTimeout

process.nextTick()是node早期版本無setImmediate時的產(chǎn)物箱舞,node作者推薦我們盡量使用setImmediate遍坟。


功能上
這兩個都能接受一個傳入的function()作為參數(shù),延遲執(zhí)行晴股。
但是在行為上
process.nextTick()在每輪事件循環(huán)中會將數(shù)組中的回調(diào)函數(shù)全部執(zhí)行愿伴,而setImmediate()只會執(zhí)行鏈表中的一個回調(diào)函數(shù)。
但是這就帶來了一個問題电湘,使用process.nextTick()推入的回調(diào)函數(shù)將會順序執(zhí)行隔节,在數(shù)組中的回調(diào)函數(shù)執(zhí)行完之前鹅经,都不會進入下次事件循環(huán),如果數(shù)組中有一個回調(diào)函數(shù)執(zhí)行時間很長怎诫,那么其他正在等待執(zhí)行的回調(diào)函數(shù)就會處于長時間等待的狀態(tài)瘾晃。
因此,當(dāng)我們需要避免這種I/O回調(diào)函數(shù)因為process.nextTick()而處于長時間等待的情況時幻妓,我們應(yīng)該使用setImmediate()執(zhí)行需要延遲執(zhí)行的任務(wù)蹦误,因為它在每輪事件循環(huán)中只會執(zhí)行一個回調(diào)函數(shù)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末肉津,一起剝皮案震驚了整個濱河市强胰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌阀圾,老刑警劉巖哪廓,帶你破解...
    沈念sama閱讀 222,590評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異初烘,居然都是意外死亡涡真,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評論 3 399
  • 文/潘曉璐 我一進店門肾筐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來哆料,“玉大人,你說我怎么就攤上這事吗铐《啵” “怎么了?”我有些...
    開封第一講書人閱讀 169,301評論 0 362
  • 文/不壞的土叔 我叫張陵唬渗,是天一觀的道長典阵。 經(jīng)常有香客問我,道長镊逝,這世上最難降的妖魔是什么壮啊? 我笑而不...
    開封第一講書人閱讀 60,078評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮撑蒜,結(jié)果婚禮上歹啼,老公的妹妹穿的比我還像新娘。我一直安慰自己座菠,他們只是感情好狸眼,可當(dāng)我...
    茶點故事閱讀 69,082評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著浴滴,像睡著了一般拓萌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上升略,一...
    開封第一講書人閱讀 52,682評論 1 312
  • 那天司志,我揣著相機與錄音甜紫,去河邊找鬼。 笑死骂远,一個胖子當(dāng)著我的面吹牛囚霸,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播激才,決...
    沈念sama閱讀 41,155評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼拓型,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了瘸恼?” 一聲冷哼從身側(cè)響起劣挫,我...
    開封第一講書人閱讀 40,098評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎东帅,沒想到半個月后压固,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,638評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡靠闭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,701評論 3 342
  • 正文 我和宋清朗相戀三年帐我,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片愧膀。...
    茶點故事閱讀 40,852評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡拦键,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出檩淋,到底是詐尸還是另有隱情芬为,我是刑警寧澤,帶...
    沈念sama閱讀 36,520評論 5 351
  • 正文 年R本政府宣布蟀悦,位于F島的核電站媚朦,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏日戈。R本人自食惡果不足惜莲镣,卻給世界環(huán)境...
    茶點故事閱讀 42,181評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望涎拉。 院中可真熱鬧,春花似錦的圆、人聲如沸鼓拧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽季俩。三九已至,卻和暖如春梅掠,著一層夾襖步出監(jiān)牢的瞬間酌住,已是汗流浹背店归。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留酪我,地道東北人消痛。 一個月前我還...
    沈念sama閱讀 49,279評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像都哭,于是被迫代替她去往敵國和親秩伞。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,851評論 2 361

推薦閱讀更多精彩內(nèi)容