溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Node.js中事件循環(huán)的機(jī)制是什么

發(fā)布時(shí)間:2022-04-01 11:03:15 來(lái)源:億速云 閱讀:168 作者:iii 欄目:編程語(yǔ)言

本篇內(nèi)容介紹了“Node.js中事件循環(huán)的機(jī)制是什么”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

先看一個(gè)demo:

setTimeout(()=>{
    console.log('timer1')
    Promise.resolve().then(function() {
        console.log('promise1')
    })}, 0)setTimeout(()=>{
    console.log('timer2')
    Promise.resolve().then(function() {
        console.log('promise2')
    })}, 0)

肉眼編譯運(yùn)行一下,蒽,在瀏覽器的結(jié)果就是下面這個(gè)了,道理都懂,就不累述了。

timer1
promise1
timer2
promise2

那么Node下執(zhí)行看看,咦。。。奇怪,跟瀏覽器的運(yùn)行結(jié)果并不一樣~

timer1
timer2
promise1
promise2

例子說(shuō)明,瀏覽器和 Node.js 的事件循環(huán)機(jī)制是有區(qū)別的,一起來(lái)看個(gè)究竟吧~

Node.js的事件處理

Node.js采用V8作為js的解析引擎,而I/O處理方面使用了自己設(shè)計(jì)的libuv,libuv是一個(gè)基于事件驅(qū)動(dòng)的跨平臺(tái)抽象層,封裝了不同操作系統(tǒng)一些底層特性,對(duì)外提供統(tǒng)一的API,事件循環(huán)機(jī)制也是它里面的實(shí)現(xiàn),核心源碼參考:

int uv_run(uv_loop_t* loop, uv_run_mode mode) {
  int timeout;
  int r;
  int ran_pending;
  r = uv__loop_alive(loop);
  if (!r)
    uv__update_time(loop);
  while (r != 0 && loop->stop_flag == 0) {
    uv__update_time(loop);
    // timers階段
    uv__run_timers(loop);
    // I/O callbacks階段
    ran_pending = uv__run_pending(loop);
    // idle階段
    uv__run_idle(loop);
    // prepare階段
    uv__run_prepare(loop);
    timeout = 0;
    if ((mode == UV_RUN_ONCE && !ran_pending) || mode == UV_RUN_DEFAULT)
      timeout = uv_backend_timeout(loop);
    // poll階段
    uv__io_poll(loop, timeout);
    // check階段
    uv__run_check(loop);
    // close callbacks階段
    uv__run_closing_handles(loop);
    if (mode == UV_RUN_ONCE) {
      uv__update_time(loop);
      uv__run_timers(loop);
    }
    r = uv__loop_alive(loop);
    if (mode == UV_RUN_ONCE || mode == UV_RUN_NOWAIT)
      break;
  }
  if (loop->stop_flag != 0)
    loop->stop_flag = 0;
  return r;
}

根據(jù)Node.js官方介紹,每次事件循環(huán)都包含了6個(gè)階段,對(duì)應(yīng)到 libuv 源碼中的實(shí)現(xiàn),如下圖所示

Node.js中事件循環(huán)的機(jī)制是什么

timers 階段:這個(gè)階段執(zhí)行timer(setTimeout、setInterval)的回調(diào)

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

idle, prepare 階段:僅node內(nèi)部使用

poll 階段:獲取新的I/O事件, 適當(dāng)?shù)臈l件下node將阻塞在這里

check 階段:執(zhí)行 setImmediate() 的回調(diào)

close callbacks 階段:執(zhí)行 socket 的 close 事件回調(diào)

我們重點(diǎn)看timers、poll、check這3個(gè)階段就好,因?yàn)槿粘i_(kāi)發(fā)中的絕大部分異步任務(wù)都是在這3個(gè)階段處理的。

timers 階段

timers 是事件循環(huán)的第一個(gè)階段,Node 會(huì)去檢查有無(wú)已過(guò)期的timer,如果有則把它的回調(diào)壓入timer的任務(wù)隊(duì)列中等待執(zhí)行,事實(shí)上,Node 并不能保證timer在預(yù)設(shè)時(shí)間到了就會(huì)立即執(zhí)行,因?yàn)镹ode對(duì)timer的過(guò)期檢查不一定靠譜,它會(huì)受機(jī)器上其它運(yùn)行程序影響,或者那個(gè)時(shí)間點(diǎn)主線程不空閑。比如下面的代碼,setTimeout() 和 setImmediate() 的執(zhí)行順序是不確定的。

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

但是把它們放到一個(gè)I/O回調(diào)里面,就一定是 setImmediate() 先執(zhí)行,因?yàn)閜oll階段后面就是check階段。

poll 階段

poll 階段主要有2個(gè)功能:

處理 poll 隊(duì)列的事件

當(dāng)有已超時(shí)的 timer,執(zhí)行它的回調(diào)函數(shù)

even loop將同步執(zhí)行poll隊(duì)列里的回調(diào),直到隊(duì)列為空或執(zhí)行的回調(diào)達(dá)到系統(tǒng)上限(上限具體多少未詳),接下來(lái)even loop會(huì)去檢查有無(wú)預(yù)設(shè)的setImmediate(),分兩種情況:

若有預(yù)設(shè)的setImmediate(), event loop將結(jié)束poll階段進(jìn)入check階段,并執(zhí)行check階段的任務(wù)隊(duì)列

若沒(méi)有預(yù)設(shè)的setImmediate(),event loop將阻塞在該階段等待

注意一個(gè)細(xì)節(jié),沒(méi)有setImmediate()會(huì)導(dǎo)致event loop阻塞在poll階段,這樣之前設(shè)置的timer豈不是執(zhí)行不了了?所以咧,在poll階段event loop會(huì)有一個(gè)檢查機(jī)制,檢查timer隊(duì)列是否為空,如果timer隊(duì)列非空,event loop就開(kāi)始下一輪事件循環(huán),即重新進(jìn)入到timer階段。

check 階段

setImmediate()的回調(diào)會(huì)被加入check隊(duì)列中, 從event loop的階段圖可以知道,check階段的執(zhí)行順序在poll階段之后。

小結(jié)

event loop 的每個(gè)階段都有一個(gè)任務(wù)隊(duì)列

當(dāng) event loop 到達(dá)某個(gè)階段時(shí),將執(zhí)行該階段的任務(wù)隊(duì)列,直到隊(duì)列清空或執(zhí)行的回調(diào)達(dá)到系統(tǒng)上限后,才會(huì)轉(zhuǎn)入下一個(gè)階段

當(dāng)所有階段被順序執(zhí)行一次后,稱 event loop 完成了一個(gè) tick

講得好有道理,可是沒(méi)有demo我還是理解不全啊,憋急,now!

const fs = require('fs')fs.readFile('test.txt', () => {
  console.log('readFile')
  setTimeout(() => {
    console.log('timeout')
  }, 0)
  setImmediate(() => {
    console.log('immediate')
  })
  })

執(zhí)行結(jié)果應(yīng)該都沒(méi)有疑問(wèn)了

readFile
immediate
timeout

Node.js 與瀏覽器的 Event Loop 差異

瀏覽器環(huán)境下,microtask的任務(wù)隊(duì)列是每個(gè)macrotask執(zhí)行完之后執(zhí)行。

Node.js中事件循環(huán)的機(jī)制是什么

而在Node.js中,microtask會(huì)在事件循環(huán)的各個(gè)階段之間執(zhí)行,也就是一個(gè)階段執(zhí)行完畢,就會(huì)去執(zhí)行microtask隊(duì)列的任務(wù)。

Node.js中事件循環(huán)的機(jī)制是什么

demo回顧

回顧文章最開(kāi)始的demo,全局腳本(main())執(zhí)行,將2個(gè)timer依次放入timer隊(duì)列,main()執(zhí)行完畢,調(diào)用??臻e,任務(wù)隊(duì)列開(kāi)始執(zhí)行;

Node.js中事件循環(huán)的機(jī)制是什么

首先進(jìn)入timers階段,執(zhí)行timer1的回調(diào)函數(shù),打印timer1,并將promise1.then回調(diào)放入microtask隊(duì)列,同樣的步驟執(zhí)行timer2,打印timer2;

至此,timer階段執(zhí)行結(jié)束,event loop進(jìn)入下一個(gè)階段之前,執(zhí)行microtask隊(duì)列的所有任務(wù),依次打印promise1、promise2。

對(duì)比瀏覽器端的處理過(guò)程:

Node.js中事件循環(huán)的機(jī)制是什么

process.nextTick() VS setImmediate()

In essence, the names should be swapped. process.nextTick() fires more immediately than setImmediate()

來(lái)自官方文檔有意思的一句話,從語(yǔ)義角度看,setImmediate() 應(yīng)該比 process.nextTick() 先執(zhí)行才對(duì),而事實(shí)相反,命名是歷史原因也很難再變。

process.nextTick() 會(huì)在各個(gè)事件階段之間執(zhí)行,一旦執(zhí)行,要直到nextTick隊(duì)列被清空,才會(huì)進(jìn)入到下一個(gè)事件階段,所以如果遞歸調(diào)用 process.nextTick(),會(huì)導(dǎo)致出現(xiàn)I/O starving(饑餓)的問(wèn)題,比如下面例子的readFile已經(jīng)完成,但它的回調(diào)一直無(wú)法執(zhí)行:

const fs = require('fs')const starttime = Date.now()let endtime
fs.readFile('text.txt', () => {
  endtime = Date.now()
  console.log('finish reading time: ', endtime - starttime)})let index = 0function handler () {
  if (index++ >= 1000) return
  console.log(`nextTick ${index}`)
  process.nextTick(handler)
  // console.log(`setImmediate ${index}`)
  // setImmediate(handler)}handler()

process.nextTick()的運(yùn)行結(jié)果:

nextTick 1
nextTick 2
......
nextTick 999
nextTick 1000
finish reading time: 170

替換成setImmediate(),運(yùn)行結(jié)果:

setImmediate 1
setImmediate 2
finish reading time: 80
......
setImmediate 999
setImmediate 1000

這是因?yàn)榍短渍{(diào)用的 setImmediate() 回調(diào),被排到了下一次event loop才執(zhí)行,所以不會(huì)出現(xiàn)阻塞。

“Node.js中事件循環(huán)的機(jī)制是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI