【譯】Node事件循環(huán)

第一次翻譯文檔,渣翻請見諒。
原文鏈接 https://nodejs.org/en/docs/guides/event-loop-timers-and-nexttick/
參考鏈接 http://www.cnblogs.com/MuYunyun/p/7287413.html
http://www.cnblogs.com/MuYunyun/p/7287413.html

什么是事件循環(huán)治唤?

事件循環(huán)是使Node.js得以執(zhí)行非阻塞I/O操作的——即使JavaScript是單線程的泽裳,通過在任何可能的時候?qū)⒉僮鱫ffload到系統(tǒng)內(nèi)核笋额。

最現(xiàn)代的內(nèi)核是多線程的冯袍,它們能處理多個操作在后臺執(zhí)行犬钢。當這些操作的其中之一完成苍鲜,內(nèi)核告訴Node.js,使得合適的callback可以被加入到poll隊列中玷犹,最終被執(zhí)行混滔。

事件循環(huán)解釋

當Node.js啟動,它初始化事件循環(huán)歹颓,處理提供的輸入腳本(或丟入REPL)坯屿,這可能導致異步API調(diào)用,調(diào)度定時器巍扛,或者調(diào)用process.nextTick()愿伴,然后開始處理事件循環(huán)。

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

每個階段有一個callback的FIFO隊列等待執(zhí)行电湘。一般來說,當事件循環(huán)進入到一個給定的階段鹅经,它會執(zhí)行這個階段的所有具體的操作寂呛,然后執(zhí)行這個階段的隊列中的callback,直到隊列好近或callback 的最大數(shù)量被執(zhí)行瘾晃。之后事件循環(huán)會移動到下一個階段贷痪。

階段概覽

  • timers: 計時器,這個階段執(zhí)行通過setTimeout()和setInterval()注冊的回調(diào)函數(shù)蹦误。
  • pending callbacks: 執(zhí)行被推遲到下個循環(huán)迭代的I/O回調(diào)函數(shù)劫拢,大部分回調(diào)將在這里被處理
  • idle, prepare: 只在內(nèi)部使用
  • poll: 輪詢,對接著要處理的I/O事件進行新的輪詢强胰,執(zhí)行與I/O相關的回調(diào)函數(shù)(幾乎所有舱沧,除了close callbacks,這個通過即時起注冊偶洋,以及setImmediate())
  • check: setImmediate()回調(diào)函數(shù)在這里被調(diào)用
  • close callbacks: 一些關閉回調(diào)函數(shù)熟吏,如socket.on('close', ...), 處理所有‘結(jié)束’事件的回調(diào)。
    在每輪事件循環(huán)期間玄窝,Node.js檢查是否在等待任何異步I/O或計時器牵寺,如果沒有,就完整關閉恩脂。

階段細節(jié)

timers

一個定時器指定閾值帽氓,一個提供的回調(diào)函數(shù)可能在這個threshold閾值之后,而不是我們想要它被執(zhí)行的exact實際的時間被執(zhí)行俩块。定時器的回調(diào)函數(shù)會在它們被指派在指定長度的時間之后盡早執(zhí)行黎休;然而浓领,操作系統(tǒng)調(diào)度或其他回調(diào)函數(shù)的運行可能會推遲它們。
注意:理論上奋渔,exact輪詢階段控制定時器何時被執(zhí)行镊逝。
例如,你設置一個timeout在100ms的閾值吼執(zhí)行嫉鲸,然后你的攪拌開始異步讀取一個文件撑蒜,讀取文件花費95ms:

const fs = require('fs');

function someAsyncOperation(callback) {
  // Assume this takes 95ms to complete
  fs.readFile('/path/to/file', callback);
}

const timeoutScheduled = Date.now();

setTimeout(() => {
  const delay = Date.now() - timeoutScheduled;

  console.log(`${delay}ms have passed since I was scheduled`);
}, 100);


// do someAsyncOperation which takes 95 ms to complete
someAsyncOperation(() => {
  const startCallback = Date.now();

  // do something that will take 10ms...
  while (Date.now() - startCallback < 10) {
    // do nothing
  }
});

當事件循環(huán)進入poll輪詢階段,它有一個空的隊列(fs.readFile()尚未完成)玄渗,因此它會等待剩余的毫秒數(shù)直到達到最快定時器的閾值座菠。當?shù)却?5ms,fs.readFile()完成讀取文件藤树,它的花費10ms來完成的回調(diào)函數(shù)被加入到poll隊列并被執(zhí)行浴滴。當回調(diào)函數(shù)完成,隊列中沒有其他的回調(diào)函數(shù)了岁钓,于是事件循環(huán)會看最快定時器的閾值已經(jīng)被達到升略,然后wrap back回到timers定時器階段來執(zhí)行定時器的回調(diào)函數(shù)。在這個例子中屡限,你會看到定時器被設置和它的回調(diào)函數(shù)被執(zhí)行的總延遲為105ms品嚣。

注意: 為了避免poll輪詢階段耗盡事件循環(huán),libuv(實現(xiàn)Node.js事件循環(huán)和平臺的所有異步行為的C庫)也有一個hard最大值(系統(tǒng)依賴)來阻止對更多事件輪詢钧大。

pending callbacks

這個階段為一些系統(tǒng)操作翰撑,如TCP錯誤類型,執(zhí)行回調(diào)函數(shù)啊央。例如眶诈,如果一個TCP套接字在嘗試連接時接收到了ECONNREFUSED,一些系統(tǒng)想等待來報告這個錯誤瓜饥。這回被放入pending callbacks的隊列中來執(zhí)行逝撬。

poll

poll輪詢階段有兩個主要的功能:

  1. 計算它該阻塞多久和輪詢I/O, 然后
  2. 處理poll隊列中的事件乓土。
    當事件循環(huán)進入poll階段球拦,且沒有定時器被設置,兩件事之一會發(fā)生:
  • 如果poll階段的隊列不為空帐我,則事件循環(huán)會遍歷回調(diào)函數(shù)的隊列坎炼,同步執(zhí)行它們,直到隊列為空或達到系統(tǒng)依賴的hard limit拦键。
  • 如果poll階段的隊列為空谣光,則以下兩件事之一會發(fā)生:
    • 如果腳本已經(jīng)被setImmediate()設置,事件循環(huán)會終止poll階段并繼續(xù)到check階段來執(zhí)行那些被設置的腳本芬为。
    • 如果腳本尚未被setImmediate()設置萄金,事件循環(huán)會等待回調(diào)函數(shù)被加入到隊列中蟀悦,然后立刻執(zhí)行它們

一旦poll隊列為空,事件循環(huán)會檢查timers定時器氧敢,它們的時間閾值被達到日戈。如果一個或多個定時器就緒,事件循環(huán)會回到timers階段來執(zhí)行那些定時器的回調(diào)函數(shù)孙乖。

check

這個階段允許我們在poll階段完成后立即執(zhí)行回調(diào)函數(shù)浙炼。如果poll階段變?yōu)殚e置,且腳本被setImmediate()設置唯袄,事件循環(huán)會來到check階段而不是等待弯屈。

setImmediate()實際是一個特殊的定時器,運行在事件循環(huán)的分開的階段恋拷。它使用一個libuvAPI资厉,這個API設置回調(diào)函數(shù)在poll階段完成后來執(zhí)行。

一般來說蔬顾,在代碼被執(zhí)行時宴偿,事件循環(huán)最終會進入poll階段,在這里它會等待一個到來的連接诀豁,請求窄刘,等等。然而且叁,如果一個回調(diào)函數(shù)已經(jīng)被setImmediate()設置且poll階段變?yōu)殚e置,它會終止并進入到check階段而不是等待poll輪詢事件秩伞。

close callbacks

如果一個socket或handle被突然關閉(e.g. socket.destroy())逞带,這個'close'事件會在這個階段被射出。否則它會通過process.nextTick()被射出纱新。

setImmediate() VS setTimeout()
兩者類似展氓,但是根據(jù)被調(diào)用的時間會發(fā)生不同的行為。

  • setImmediate()被設計為一旦現(xiàn)在的poll階段完成就執(zhí)行脸爱。
  • setTimeout()設置一個腳本在一個最小毫米單位閾值過去之后執(zhí)行遇汞。

定時器被執(zhí)行的順序會根據(jù)它們被調(diào)用時處在的上下文而變化。如果都在主模塊中被調(diào)用簿废,則timing會被進程的性能限制(進程的性能可能會被機器上運行的其他應用程序影響)空入。

例如,如果我們運行以下的不處于一個I/O cycle(也就是主模塊)內(nèi)部的腳本族檬,這兩個定時器被執(zhí)行的順序是不一定的歪赢,因為受到了進程性能的限制:

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

setImmediate(() => {
  console.log('immediate');
});
$ node timeout_vs_immediate.js
timeout
immediate

$ node timeout_vs_immediate.js
immediate
timeout

然而,如果你把兩個調(diào)用移動到一個I/O cycle里面单料,immediate 回調(diào)函數(shù)總是先執(zhí)行:

// timeout_vs_immediate.js
const fs = require('fs');

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

$ node timeout_vs_immediate.js
immediate
timeout

使用setImmediate()而不是setTimeout()的主要優(yōu)點是埋凯,當處在I/O cycle之內(nèi)時点楼,前者總是在任何其他定時器之前執(zhí)行,不管當前有多少個定時器白对。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末掠廓,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子甩恼,更是在濱河造成了極大的恐慌蟀瞧,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,865評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件媳拴,死亡現(xiàn)場離奇詭異黄橘,居然都是意外死亡,警方通過查閱死者的電腦和手機屈溉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,296評論 3 399
  • 文/潘曉璐 我一進店門塞关,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人子巾,你說我怎么就攤上這事帆赢。” “怎么了线梗?”我有些...
    開封第一講書人閱讀 169,631評論 0 364
  • 文/不壞的土叔 我叫張陵椰于,是天一觀的道長。 經(jīng)常有香客問我仪搔,道長瘾婿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,199評論 1 300
  • 正文 為了忘掉前任烤咧,我火速辦了婚禮偏陪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘煮嫌。我一直安慰自己笛谦,他們只是感情好,可當我...
    茶點故事閱讀 69,196評論 6 398
  • 文/花漫 我一把揭開白布昌阿。 她就那樣靜靜地躺著饥脑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪懦冰。 梳的紋絲不亂的頭發(fā)上灶轰,一...
    開封第一講書人閱讀 52,793評論 1 314
  • 那天,我揣著相機與錄音刷钢,去河邊找鬼框往。 笑死,一個胖子當著我的面吹牛闯捎,可吹牛的內(nèi)容都是我干的椰弊。 我是一名探鬼主播许溅,決...
    沈念sama閱讀 41,221評論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼秉版!你這毒婦竟也來了贤重?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,174評論 0 277
  • 序言:老撾萬榮一對情侶失蹤清焕,失蹤者是張志新(化名)和其女友劉穎并蝗,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體秸妥,經(jīng)...
    沈念sama閱讀 46,699評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡滚停,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,770評論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了粥惧。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片键畴。...
    茶點故事閱讀 40,918評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖突雪,靈堂內(nèi)的尸體忽然破棺而出起惕,到底是詐尸還是另有隱情,我是刑警寧澤咏删,帶...
    沈念sama閱讀 36,573評論 5 351
  • 正文 年R本政府宣布惹想,位于F島的核電站,受9級特大地震影響督函,放射性物質(zhì)發(fā)生泄漏嘀粱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,255評論 3 336
  • 文/蒙蒙 一辰狡、第九天 我趴在偏房一處隱蔽的房頂上張望锋叨。 院中可真熱鬧,春花似錦搓译、人聲如沸悲柱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,749評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至嘿般,卻和暖如春段标,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背炉奴。 一陣腳步聲響...
    開封第一講書人閱讀 33,862評論 1 274
  • 我被黑心中介騙來泰國打工逼庞, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人瞻赶。 一個月前我還...
    沈念sama閱讀 49,364評論 3 379
  • 正文 我出身青樓赛糟,卻偏偏與公主長得像派任,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子璧南,可洞房花燭夜當晚...
    茶點故事閱讀 45,926評論 2 361