Node的事件循環(huán)

1.同步任務(wù)與異步任務(wù)
(1)同步任務(wù):在主線程上排隊(duì)執(zhí)行的任務(wù)于樟,只有前一個(gè)任務(wù)執(zhí)行完畢衬以,才能執(zhí)行下一個(gè)任務(wù)便贵;
(2)異步任務(wù):不進(jìn)入主線程而是進(jìn)入任務(wù)隊(duì)列的任務(wù)嘲碧,只有等主線程的任務(wù)執(zhí)行完畢后,任務(wù)隊(duì)列開始通知主線程椎瘟,請(qǐng)求將異步任務(wù)進(jìn)入到主線程執(zhí)行覆致;

2.瀏覽器環(huán)境與node環(huán)境的事件循環(huán)機(jī)制
(1)瀏覽器環(huán)境:在HTML5中定義的規(guī)范
??js執(zhí)行為單線程(不考慮web worker)肺蔚,所有代碼皆在執(zhí)行線程調(diào)用棧完成執(zhí)行煌妈;當(dāng)執(zhí)行線程任務(wù)清空后才會(huì)去輪詢?nèi)∪蝿?wù)隊(duì)列中任務(wù)。

  • 任務(wù)隊(duì)列
    ?? 瀏覽器對(duì)不同的異步操作婆排,將其添加到任務(wù)隊(duì)列的時(shí)機(jī)也不同—由瀏覽器內(nèi)核的webcore來執(zhí)行声旺,其包含3種webAPI:
    • DOM Binding:處理DOM綁定事件,若綁定事件觸發(fā)時(shí)段只,回調(diào)函數(shù)立即被webcore添加到任務(wù)隊(duì)列中腮猖;
    • network:處理ajax請(qǐng)求,在網(wǎng)絡(luò)請(qǐng)求返回時(shí)赞枕,才將對(duì)應(yīng)的回調(diào)函數(shù)添加到隊(duì)列中澈缺;
    • timer:對(duì)setTimeout等計(jì)時(shí)器進(jìn)行延時(shí)處理,當(dāng)時(shí)間到達(dá)時(shí)才會(huì)將回調(diào)函數(shù)添加到任務(wù)隊(duì)列中炕婶;
  • 異步任務(wù)類別及執(zhí)行順序
    • macrotask(宏任務(wù)—task):script中代碼姐赡、setTimeout、setInterval柠掂、I/O项滑、UI render。
    • microtask(微任務(wù)): promise涯贞、Object.observe枪狂、MutationObserver。


      瀏覽器異步任務(wù)執(zhí)行順序
      • 具體過程
        (1)執(zhí)行完主執(zhí)行線程中的任務(wù)(初始執(zhí)行線程中沒有代碼宋渔,每一個(gè)script標(biāo)簽中的代碼是一個(gè)獨(dú)立的macrotask)州疾。
        (2)取出Microtask Queue中任務(wù)執(zhí)行直到清空(若microtask一直被添加,則會(huì)繼續(xù)執(zhí)行microtask皇拣,卡死m(xù)acrotask)严蓖。
        (3)取出Macrotask Queue中一個(gè)任務(wù)執(zhí)行。
        (4)取出Microtask Queue中任務(wù)執(zhí)行直到清空氧急。
        (5)重復(fù)(3)和(4)

(2)node環(huán)境:由libuv庫實(shí)現(xiàn)颗胡;
??node基于事件循環(huán)實(shí)現(xiàn)非阻塞和事件驅(qū)動(dòng),其事件循環(huán)按階段執(zhí)行态蒂;

Node中的事件循環(huán)階段:每個(gè)階段都有對(duì)應(yīng)的任務(wù)隊(duì)列杭措,一次tick就是完成所有階段的一次執(zhí)行

  • 階段詳情
    (1)timers(定時(shí)器階段):處理setTimeout()和setInterval()設(shè)定的回調(diào)函數(shù)隊(duì)列;

     一個(gè)timer事件指定一個(gè)下限時(shí)間而不是準(zhǔn)確的時(shí)間钾恢,在達(dá)到這個(gè)下限時(shí)間后+主線程空閑時(shí)手素,執(zhí)行該事件對(duì)應(yīng)的回調(diào)函數(shù)鸳址,從技術(shù)上來說,poll階段控制timers什么時(shí)候執(zhí)行泉懦,而執(zhí)行的具體位置在timers(poll階段會(huì)控制是否進(jìn)入下個(gè)timers階段)稿黍;
    

    (2)I/O callbacks階段:執(zhí)行一些系統(tǒng)操作的回調(diào)(比如網(wǎng)絡(luò)通信的錯(cuò)誤回調(diào));

    (3)idle崩哩、prepare:僅供libuv內(nèi)部調(diào)用巡球;

    (4)poll(輪詢階段): 等待還未返回的I/O事件,任何異步方法(除timers邓嘹、setImmediate酣栈、close外)完成時(shí),都會(huì)將其加到poll queue里汹押,并立即執(zhí)行矿筝;

    • 主要功能:

      (i) 處理poll隊(duì)列里的事件;
      (ii)執(zhí)行下限時(shí)間已經(jīng)達(dá)到的timers的回調(diào)(進(jìn)入下一個(gè)事件循環(huán)) 棚贾;

    • 當(dāng)事件循環(huán)進(jìn)入poll階段:
      (i)poll隊(duì)列不為空的時(shí)候窖维,事件循環(huán)肯定是先遍歷隊(duì)列并同步執(zhí)行回調(diào),直到隊(duì)列清空或執(zhí)行回調(diào)數(shù)達(dá)到系統(tǒng)上限妙痹。
      (ii)poll隊(duì)列為空的時(shí)候铸史,這里有兩種情況。
      1)如果代碼已經(jīng)被setImmediate()設(shè)定了回調(diào)怯伊,那么事件循環(huán)直接結(jié)束poll階段進(jìn)入check階段來執(zhí)行check隊(duì)列里的回調(diào)琳轿。
      2)如果代碼沒有被設(shè)定setImmediate()設(shè)定回調(diào):
      * 如果有被設(shè)定的timers,那么此時(shí)事件循環(huán)會(huì)檢查timers耿芹,如果有一個(gè)或多個(gè)timers下限時(shí)間已經(jīng)到達(dá)利赋,那么事件循環(huán)將繞回timers階段,并執(zhí)行timers的有效回調(diào)隊(duì)列(進(jìn)入下一個(gè)事件循環(huán)階段了)猩系。
      * 如果沒有被設(shè)定timers,這個(gè)時(shí)候事件循環(huán)是阻塞在poll階段等待回調(diào)被加入poll隊(duì)列中燥。

    (5)check階段:執(zhí)行setImmediate()設(shè)定的回調(diào)寇甸;
    * setImmediate()實(shí)際上是一個(gè)特殊的timer,跑在事件循環(huán)中的一個(gè)獨(dú)立的階段疗涉;它使用libuv的API來設(shè)定在:
    * poll階段結(jié)束后立即執(zhí)行回調(diào)拿霉;
    * poll階段空閑時(shí),不讓阻塞在poll階段直接跳到check階段執(zhí)行回調(diào)咱扣。

    (6)close callbacks階段:如果一個(gè)socket或handle被突然關(guān)掉(比如socket.destroy())绽淘,close事件將在這個(gè)階段被觸發(fā),否則將通過process.nextTick()觸發(fā)闹伪。

  • 任務(wù)隊(duì)列類型
    原生的libuv事件循環(huán)處理的隊(duì)列有4種主要類型:
    (1)Timers Queue沪铭;
    (2)I/O Queue壮池;
    (3)Check Queue;
    (4)Close Queue杀怠;
    中間隊(duì)列有2種:
    (1)Next tick隊(duì)列:process.nextTick()
    (2)Other Microtasks:包括其他 microtask椰憋,如 resolved promise回調(diào);
    ??** Next tick隊(duì)列比Other Microtasks隊(duì)列具有更高的優(yōu)先級(jí)**赔退;不過橙依,它們都在事件循環(huán)的兩個(gè)階段之間進(jìn)行處理,也就是在結(jié)束一個(gè)階段后libuv通信回傳到上層硕旗;

    注:NodeJS中不同類型的事件在自己的隊(duì)列中排隊(duì)窗骑;中間隊(duì)列是只要一個(gè)階段完成,事件循環(huán)就會(huì)檢查這兩個(gè)中間隊(duì)列是否有可執(zhí)行的任務(wù)漆枚,若有則立即處理它們直到為空创译,一旦為空,事件循環(huán)將繼續(xù)到下一個(gè)階段浪读。

    一次tick的流程

    • 具體過程
      (1)清空當(dāng)前循環(huán)內(nèi)的Timers Queue昔榴,清空NextTick Queue,清空Microtask Queue碘橘。
      (2)清空當(dāng)前循環(huán)內(nèi)的I/O Queue互订,清空NextTick Queue,清空Microtask Queue痘拆。
      (3)清空當(dāng)前循環(huán)內(nèi)的Check Queu仰禽,清空NextTick Queue,清空Microtask Queue纺蛆。
      (4)清空當(dāng)前循環(huán)內(nèi)的Close Queu吐葵,清空NextTick Queue,清空Microtask Queue桥氏。
      (5)進(jìn)入下輪循環(huán)(tick)温峭;

4.代碼

function sleep(time) {
  let startTime = new Date()
  while (new Date() - startTime < time) {}
  console.log('1s over')
}
setTimeout(() => {
  console.log('setTimeout - 1')
  setTimeout(() => {
      console.log('setTimeout - 1 - 1')
      sleep(1000)
  })
  new Promise(resolve => resolve()).then(() => {
      console.log('setTimeout - 1 - then')
      new Promise(resolve => resolve()).then(() => {
          console.log('setTimeout - 1 - then - then')
      })
  })
  sleep(1000)
})

setTimeout(() => {
  console.log('setTimeout - 2')
  setTimeout(() => {
      console.log('setTimeout - 2 - 1')
      sleep(1000)
  })
  new Promise(resolve => resolve()).then(() => {
      console.log('setTimeout - 2 - then')
      new Promise(resolve => resolve()).then(() => {
          console.log('setTimeout - 2 - then - then')
      })
  })
  sleep(1000)
})
瀏覽器輸出

node輸出

6.Node的異步I/O模型
(1)基本要素:事件循環(huán)、觀察者字支、請(qǐng)求對(duì)象凤藏、IO線程池;

  • 事件循環(huán):典型的生產(chǎn)者/消費(fèi)者模型堕伪;

    • 事件的產(chǎn)生:網(wǎng)絡(luò)請(qǐng)求揖庄、文件IO等操作;
    • 事件的消費(fèi):主線程空閑時(shí)從觀察者那兒取出事件并處理其回調(diào)欠雌;
  • 觀察者:在每個(gè)Tick的過程中蹄梢,通過觀察者判斷是否有事件需要處理;

      小劇場:
      * 主線程:飯館的廚房富俄;
      * 觀察者:收銀臺(tái)的小妹禁炒;
      * 事件及回調(diào)函數(shù):客人的點(diǎn)單而咆;
      * 劇情:廚房一輪一輪炒菜,但是具體要炒什么菜取決于收銀臺(tái)收到的客人的下單齐苛。
    
  • 請(qǐng)求對(duì)象: 從js發(fā)起回調(diào)到內(nèi)核執(zhí)行完IO操作的過渡過程中的中間產(chǎn)物翘盖;

      以fs.open(path,flags,[mode],callback)打開某個(gè)文件為例:從js調(diào)用Node的核心模塊->核心模塊調(diào)用C++內(nèi)建模塊->內(nèi)建模塊調(diào)用libuv進(jìn)行系統(tǒng)調(diào)用:uv_fs_open():
      (1)創(chuàng)建一個(gè)FSReqWrap請(qǐng)求對(duì)象:封裝js層傳入的參數(shù)和open()方法,將回調(diào)函數(shù)設(shè)置到該對(duì)象的oncomplete_sym屬性上;
      (2)將這個(gè)請(qǐng)求對(duì)象推入線程池中等待執(zhí)行:當(dāng)線程池有可用線程時(shí)凹蜂,調(diào)用相應(yīng)的底層函數(shù):fs_open()馍驯;
       js調(diào)用完后立即返回,js線程可以繼續(xù)執(zhí)行當(dāng)前任務(wù)的后續(xù)操作玛痊,當(dāng)前的I/O操作在線程池中等待操作汰瘫,不影響js線程。
    
fs.open()流程圖
  • 執(zhí)行回調(diào):以windows平臺(tái)為例
    • 線程池中的I/O操作調(diào)用完后擂煞,會(huì)將獲取的結(jié)果存儲(chǔ)到req->result屬性上混弥,調(diào)用PostQueuedCompletionStatus()向IOCP(IO完成端口)提交執(zhí)行狀態(tài),告知當(dāng)前對(duì)象操作已經(jīng)完成对省,并將線程歸還線程池蝗拿;
    • I/O觀察者在每次Tick的執(zhí)行中,調(diào)用GetQueuedCompletionStatus()檢查線程池是否有執(zhí)行完的請(qǐng)求蒿涎,若存在哀托,將請(qǐng)求對(duì)象加入到I/O觀察者隊(duì)列中,然后將其當(dāng)作事件處理:取出請(qǐng)求對(duì)象的result屬性做參數(shù)劳秋,取出oncomplete_sym屬性做方法仓手,然后調(diào)用執(zhí)行。

(2)基本流程

  • 第一階段:組裝好對(duì)象 -> 送入IO線程池等待執(zhí)行玻淑;
  • 第二階段:回調(diào)通知嗽冒;


    node中整個(gè)異步IO的流程

[參考文獻(xiàn)]
1.NodeJS事件循環(huán)(英文版/中文版)
2.node中的Event模塊
3.瀏覽器和Node不同的事件循環(huán)(Event Loop)
4.《深入淺出nodejs》

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市补履,隨后出現(xiàn)的幾起案子添坊,更是在濱河造成了極大的恐慌,老刑警劉巖箫锤,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件帅腌,死亡現(xiàn)場離奇詭異,居然都是意外死亡麻汰,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門戚篙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來五鲫,“玉大人,你說我怎么就攤上這事岔擂∥晃梗” “怎么了浪耘?”我有些...
    開封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長塑崖。 經(jīng)常有香客問我七冲,道長,這世上最難降的妖魔是什么规婆? 我笑而不...
    開封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任澜躺,我火速辦了婚禮,結(jié)果婚禮上抒蚜,老公的妹妹穿的比我還像新娘掘鄙。我一直安慰自己,他們只是感情好嗡髓,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開白布操漠。 她就那樣靜靜地躺著,像睡著了一般饿这。 火紅的嫁衣襯著肌膚如雪浊伙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天长捧,我揣著相機(jī)與錄音嚣鄙,去河邊找鬼。 笑死唆姐,一個(gè)胖子當(dāng)著我的面吹牛拗慨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播奉芦,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼赵抢,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了声功?” 一聲冷哼從身側(cè)響起烦却,我...
    開封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎先巴,沒想到半個(gè)月后其爵,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡伸蚯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年摩渺,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片剂邮。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡摇幻,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情绰姻,我是刑警寧澤枉侧,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站狂芋,受9級(jí)特大地震影響榨馁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜帜矾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一翼虫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧黍特,春花似錦蛙讥、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至翔曲,卻和暖如春迫像,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背瞳遍。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來泰國打工闻妓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人掠械。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓由缆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親猾蒂。 傳聞我的和親對(duì)象是個(gè)殘疾皇子均唉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

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