亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

Node中的事件循環(huán)和異步API

atinosun / 1730人閱讀

摘要:異步在中,是在單線程中執(zhí)行的沒錯,但是內(nèi)部完成工作的另有線程池,使用一個主進程和多個線程來模擬異步。在事件循環(huán)中,觀察者會不斷的找到線程池中已經(jīng)完成的請求對象,從中取出回調(diào)函數(shù)和數(shù)據(jù)并執(zhí)行。

1. 介紹

單線程編程會因阻塞I/O導(dǎo)致硬件資源得不到更優(yōu)的使用。多線程編程也因為編程中的死鎖、狀態(tài)同步等問題讓開發(fā)人員頭痛。
Node在兩者之間給出了它的解決方案:利用單線程,遠離多線程死鎖、狀態(tài)同步等問題;利用異步I/O,讓單線程遠離阻塞,以好使用CPU。

實際上,node只是在應(yīng)用層屬于單線程,底層其實通過libuv維護了一個阻塞I/O調(diào)用的線程池。

但是:在應(yīng)用層面,JS是單線程的,業(yè)務(wù)代碼中不能存在耗時過長的代碼,否則可能會嚴重拖后續(xù)代碼(包括回調(diào))的處理。如果遇到需要復(fù)雜的業(yè)務(wù)計算時,應(yīng)當(dāng)想辦法啟用獨立進程或交給其他服務(wù)進行處理。

1.1 異步I/O

在Node中,JS是在單線程中執(zhí)行的沒錯,但是內(nèi)部完成I/O工作的另有線程池,使用一個主進程和多個I/O線程來模擬異步I/O。
當(dāng)主線程發(fā)起I/O調(diào)用時,I/O操作會被放在I/O線程來執(zhí)行,主線程繼續(xù)執(zhí)行下面的任務(wù),在I/O線程完成操作后會帶著數(shù)據(jù)通知主線程發(fā)起回調(diào)。

1.2 事件循環(huán)

事件循環(huán)是Node的執(zhí)行模型,正是這種模型使得回調(diào)函數(shù)非常普遍。
在進程啟動時,Node便會創(chuàng)建一個類似while(true)的循環(huán),執(zhí)行每次循環(huán)的過程就是判斷有沒有待處理的事件,如果有,就取出事件及其相關(guān)的回調(diào)并執(zhí)行他們,然后進入下一個循環(huán)。如果不再有事件處理,就退出進程。

Event loop是一種程序結(jié)構(gòu),是實現(xiàn)異步的一種機制。Event loop可以簡單理解為:

所有任務(wù)都在主線程上執(zhí)行,形成一個執(zhí)行棧(execution context stack)。

主線程之外,還存在一個"任務(wù)隊列"(task queue)。系統(tǒng)把異步任務(wù)放到"任務(wù)隊列"之中,然后主線程繼續(xù)執(zhí)行后續(xù)的任務(wù)。

一旦"執(zhí)行棧"中的所有任務(wù)執(zhí)行完畢,系統(tǒng)就會讀取"任務(wù)隊列"。如果這個時候,異步任務(wù)已經(jīng)結(jié)束了等待狀態(tài),就會從"任務(wù)隊列"進入執(zhí)行棧,恢復(fù)執(zhí)行。

主線程不斷重復(fù)上面的第三步。

Node中事件循環(huán)階段解析:

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

每個階段都有一個FIFO的回調(diào)隊列(queue)要執(zhí)行。而每個階段有自己的特殊之處,簡單說,就是當(dāng)event loop進入某個階段后,會執(zhí)行該階段特定的(任意)操作,然后才會執(zhí)行這個階段的隊列里的回調(diào)。當(dāng)隊列被執(zhí)行完,或者執(zhí)行的回調(diào)數(shù)量達到上限后,event loop會進入下個階段。

Phases Overview 階段總覽

timers: 這個階段執(zhí)行setTimeout()setInterval()設(shè)定的回調(diào)。

I/O callbacks: 執(zhí)行幾乎所有的回調(diào),除了close callbackssetTimeout()、setInterval()、setImmediate()的回調(diào)。

idle, prepare: 僅內(nèi)部使用。

poll: 獲取新的I/O事件;node會在適當(dāng)條件下阻塞在這里。

check: 執(zhí)行setImmediate()設(shè)定的回調(diào)。

close callbacks: 執(zhí)行比如socket.on("close", ...)的回調(diào)。

1. timers

一個timer指定一個下限時間而不是準確時間,定時器setTimeout()setInterval()在達到這個下限時間后執(zhí)行回調(diào)。在指定的時間過后,timers會盡早的執(zhí)行回調(diào),但是系統(tǒng)調(diào)度或者其他回調(diào)的執(zhí)行可能會延遲它們。
從技術(shù)上來說,poll階段控制timers什么時候執(zhí)行,而執(zhí)行的具體位置在timers。
下限的時間有一個范圍:[1, 2147483647],如果設(shè)定的時間不在這個范圍,將被設(shè)置為1。

2. I/O callbacks

執(zhí)行除了close callbacks、setTimeout()、setInterval()、setImmediate()回調(diào)之外幾乎所有回調(diào),比如說TCP連接發(fā)生錯誤。

3. idle, prepare

系統(tǒng)內(nèi)部的一些調(diào)用。

4. poll

這是最復(fù)雜的一個階段。poll會檢索新的I/O events,并且會在合適的時候阻塞,等待回調(diào)被加入。

poll階段有兩個主要的功能:一是執(zhí)行下限時間已經(jīng)達到的timers的回調(diào),一是處理poll隊列里的事件。
注:Node很多API都是基于事件訂閱完成的,這些API的回調(diào)應(yīng)該都在poll階段完成。

當(dāng)事件循環(huán)進入poll階段:

poll隊列不為空的時候,事件循環(huán)肯定是先遍歷隊列并同步執(zhí)行回調(diào),直到隊列清空或執(zhí)行回調(diào)數(shù)達到系統(tǒng)上限。

poll隊列為空的時候,這里有兩種情況。

如果代碼已經(jīng)被setImmediate()設(shè)定了回調(diào),那么事件循環(huán)直接結(jié)束poll階段進入check階段來執(zhí)行check隊列里的回調(diào)。

如果代碼沒有被設(shè)定setImmediate()設(shè)定回調(diào):

如果有被設(shè)定的timers,那么此時事件循環(huán)會檢查timers,如果有一個或多個timers下限時間已經(jīng)到達,那么事件循環(huán)將繞回timers階段,并執(zhí)行timers的有效回調(diào)隊列。

如果沒有被設(shè)定timers,這個時候事件循環(huán)是阻塞在poll階段等待事件回調(diào)被加入poll隊列。

Node的很多API都是基于事件訂閱完成的,比如fs.readFile,這些回調(diào)應(yīng)該都在poll階段完成。

5. check

setImmediate()在這個階段執(zhí)行。

這個階段允許在poll階段結(jié)束后立即執(zhí)行回調(diào)。如果poll階段空閑,并且有被setImmediate()設(shè)定的回調(diào),那么事件循環(huán)直接跳到check執(zhí)行而不是阻塞在poll階段等待poll 事件們 (poll events)被加入。

注意:如果進行到了poll階段,setImmediate()具有最高優(yōu)先級,只要poll隊列為空且注冊了setImmediate(),無論是否有timers達到下限時間,setImmediate()的代碼都先執(zhí)行。

6. close callbacks

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

1.3 請求對象

對于Node中的異步I/O調(diào)用而言,回調(diào)函數(shù)不由開發(fā)者來調(diào)用,從JS發(fā)起調(diào)用到I/O操作完成,存在一個中間產(chǎn)物,叫請求對象
在JS發(fā)起調(diào)用后,JS調(diào)用Node的核心模塊,核心模塊調(diào)用C++內(nèi)建模塊,內(nèi)建模塊通過libuv判斷平臺并進行系統(tǒng)調(diào)用。在進行系統(tǒng)調(diào)用時,從JS層傳入的方法和參數(shù)都被封裝在一個請求對象中,請求對象被放在線程池中等待執(zhí)行。JS立即返回繼續(xù)后續(xù)操作。

1.4 執(zhí)行回調(diào)

在線程可用時,線程會取出請求對象來執(zhí)行I/O操作,執(zhí)行完后將結(jié)果放在請求對象中,并歸還線程。
在事件循環(huán)中,I/O觀察者會不斷的找到線程池中已經(jīng)完成的請求對象,從中取出回調(diào)函數(shù)和數(shù)據(jù)并執(zhí)行。

跑完當(dāng)前執(zhí)行環(huán)境下能跑完的代碼。每一個事件消息都被運行直到完成為止,在此之前,任何其他事件都不會被處理。這和C等一些語言不通,它們可能在一個線程里面,函數(shù)跑著跑著突然停下來,然后其他線程又跑起來了。JS這種機制的一個典型的壞處,就是當(dāng)某個事件處理耗時過長時,后面的事件處理都會被延后,直到這個事件處理結(jié)束,在瀏覽器環(huán)境中運行時,可能會出現(xiàn)某個腳本運行時間過長,頁面無響應(yīng)的提示。Node環(huán)境則可能出現(xiàn)大量用戶請求被掛起,不能及時響應(yīng)的情況。

2. 非I/O的異步API

Node中除了異步I/O之外,還有一些與I/O無關(guān)的異步API,分別是:setTimeout()setInterval()、process.nextTick()、setImmediate(),他們并不是像普通I/O操作那樣真的需要等待事件異步處理結(jié)束再進行回調(diào),而是出于定時或延遲處理的原因才設(shè)計的。

2.1 setTimeout()setInterval()

這兩個方法實現(xiàn)原理與異步I/O相似,只不過不用I/O線程池的參與。
使用它們創(chuàng)建的定時器會被放入timers隊列的一個紅黑樹中,每次事件循環(huán)執(zhí)行時會從相應(yīng)隊列中取出并判斷是否超過定時時間,超過就形成一個事件,回調(diào)立即執(zhí)行。
所以,和瀏覽器中一樣,這個并不精確,會被長時間的同步事件阻塞。

值得一提的是,在Node的setTimeout的源碼中:

// Node源碼
  after *= 1; // coalesce to number or NaN
  if (!(after >= 1 && after <= TIMEOUT_MAX)) {
    if (after > TIMEOUT_MAX) {
      process.emitWarning(...);
    }
    after = 1; // schedule on next tick, follows browser behavior
  }

意思是如果沒有設(shè)置這個after,或者小于1,或者大于TIMEOUT_MAX(2^31-1),都會被強制設(shè)置為1ms。也就是說setTimeout(xxx,0)其實等同于setTimeout(xxx,1)。

2.2 setImmediate()

setImmediate()是放在check階段執(zhí)行的,實際上是一個特殊的timer,跑在event loop中一個獨立的階段。它使用libuv的API來設(shè)定在 poll 階段結(jié)束后立即執(zhí)行回調(diào)。
來看看這個例子:

setTimeout(function() {
  console.log("setTimeout")
}, 0)
setImmediate(function() {
  console.log("setImmediate")
})                                // 輸出不穩(wěn)定

setTimeout與setImmediate先后入隊之后,首先進入的是timers階段,如果我們的機器性能一般或者加入了一個同步長耗時操作,那么進入timers階段,1ms已經(jīng)過去了,那么setTimeout的回調(diào)會首先執(zhí)行。
如果沒有到1ms,那么在timers階段的時候,超時時間沒到,setTimeout回調(diào)不執(zhí)行,事件循環(huán)來到了poll階段,這個時候隊列為空,此時有代碼被setImmediate(),于是先執(zhí)行了setImmediate()的回調(diào)函數(shù),之后在下一個事件循環(huán)再執(zhí)行setTimemout的回調(diào)函數(shù)。

setTimeout(function() {
  console.log("set timeout")
}, 0)
setImmediate(function() {
  console.log("set Immediate")
})
for (let i = 0; i < 100000; i++) {}           // 可以保證執(zhí)行時間超過1ms
// 穩(wěn)定輸出: setTimeout    setImmediate

這樣就可以穩(wěn)定輸出了。

再一個栗子:

const fs = require("fs")
fs.readFile("./filePath.js", (err, data) => {
  setTimeout(() => console.log("setTimeout") , 0)
  setImmediate(() => console.log("setImmediate"))
  console.log("開始了")
  for (let i = 0; i < 100000; i++) {}        
})                                         // 輸出 開始了 setImmediate setTimeout

這里我們就會發(fā)現(xiàn),setImmediate永遠先于setTimeout執(zhí)行。
fs.readFile的回調(diào)是在poll階段執(zhí)行的,當(dāng)其回調(diào)執(zhí)行完畢之后,setTimeout與setImmediate先后入了timerscheck的隊列,繼續(xù)到pollpoll隊列為空,此時發(fā)現(xiàn)有setImmediate,于是事件循環(huán)先進入check階段執(zhí)行回調(diào),之后在下一個事件循環(huán)再在timers階段中執(zhí)行setTimeout回調(diào),雖然這個setTimeout已經(jīng)到了超時時間。

再來個栗子:
同樣的,這段代碼也是一樣的道理:

setTimeout(() => {
    setImmediate(() => console.log("setImmediate") );
    setTimeout(() => console.log("setTimeout") , 0);
}, 0);

以上的代碼在timers階段執(zhí)行外部的setTimeout回調(diào)后,內(nèi)層的setTimeout和setImmediate入隊,之后事件循環(huán)繼續(xù)往后面的階段走,走到poll階段的時候發(fā)現(xiàn)隊列為空,此時有代碼被setImmedate(),所以直接進入check階段執(zhí)行響應(yīng)回調(diào)(注意這里沒有去檢測timers隊列中是否有成員到達超時事件,因為setImmediate()優(yōu)先)。之后在下一個事件循環(huán)的timers階段中再去執(zhí)行相應(yīng)的回調(diào)。

2.3 process.nextTick()Promise

對于這兩個,我們可以把它們理解成一個微任務(wù)。也就是說,它們其實不屬于事件循環(huán)的一部分。

有時我們想要立即異步執(zhí)行一個任務(wù),可能會使用延時為0的定時器,但是這樣開銷很大。我們可以換而使用process.nextTick(),它會將傳入的回調(diào)放入nextTickQueue隊列中,下一輪Tick之后取出執(zhí)行,不管事件循環(huán)進行到什么地步,都在當(dāng)前執(zhí)行棧的操作結(jié)束的時候調(diào)用,參見Nodejs官網(wǎng)。

process.nextTick方法指定的回調(diào)函數(shù),總是在當(dāng)前執(zhí)行隊列的尾部觸發(fā),多個process.nextTick語句總是一次執(zhí)行完(不管它們是否嵌套),遞歸調(diào)用process.nextTick,將會沒完沒了,主線程根本不會去讀取事件隊列,導(dǎo)致阻塞后續(xù)調(diào)用,直至達到最大調(diào)用限制。

相比于在定時器中采用紅黑樹樹的操作時間復(fù)雜度為0(lg(n)),而process.nextTick()的時間復(fù)雜度為0(1),相比之下更高效。

來舉一個復(fù)雜的栗子,這個栗子搞懂基本上就全部理解了:

setTimeout(() => {
  process.nextTick(() => console.log("nextTick1"))
  
  setTimeout(() => {
    console.log("setTimout1")
    process.nextTick(() => {
      console.log("nextTick2")
      setImmediate(() => console.log("setImmediate1"))
      process.nextTick(() => console.log("nextTick3"))
    })
    setImmediate(() => console.log("setImmediate2"))
    process.nextTick(() => console.log("nextTick4"))
    console.log("sync2")
    setTimeout(() => console.log("setTimout2"), 0)
  }, 0)
  
  console.log("sync1")
}, 0) 
// 輸出: sync1 nextTick1 setTimout1 sync2 nextTick2 nextTick4 nextTick3 setImmediate2 setImmediate1 setTimout2
2.4 結(jié)論

process.nextTick(),效率最高,消費資源小,但會阻塞CPU的后續(xù)調(diào)用;

setTimeout(),精確度不高,可能有延遲執(zhí)行的情況發(fā)生,且因為動用了紅黑樹,所以消耗資源大;

setImmediate(),消耗的資源小,也不會造成阻塞,但效率也是最低的。

網(wǎng)上的帖子大多深淺不一,甚至有些前后矛盾,在下的文章都是學(xué)習(xí)過程中的總結(jié),如果發(fā)現(xiàn)錯誤,歡迎留言指出~

參考:
Node——異步I/O
Node探秘之事件循環(huán)
Node探秘之事件循環(huán)--setTimeout/setImmediate/process.nextTick的差別
細說setTimeout/setImmediate/process.nextTick的區(qū)別
深入淺出Nodejs
Node官方文檔
由setTimeout和setImmediate執(zhí)行順序的隨機性窺探Node的事件循環(huán)機制
Node.js的event loop及timer/setImmediate/nextTick
Node.js 探秘:初識單線程的 Node.js | Taobao FED | 淘寶前端團隊
Node.js 事件循環(huán)機制 - 一像素 - 博客園

PS:歡迎大家關(guān)注我的公眾號【前端下午茶】,一起加油吧~

另外可以加入「前端下午茶交流群」微信群,長按識別下面二維碼即可加我好友,備注加群,我拉你入群~

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/90536.html

相關(guān)文章

  • Node.js設(shè)計模式》Node.js基本模式

    摘要:回調(diào)函數(shù)是在異步操作完成后傳播其操作結(jié)果的函數(shù),總是用來替代同步操作的返回指令。下面的圖片顯示了中事件循環(huán)過程當(dāng)異步操作完成時,執(zhí)行權(quán)就會交給這個異步操作開始的地方,即回調(diào)函數(shù)。 本系列文章為《Node.js Design Patterns Second Edition》的原文翻譯和讀書筆記,在GitHub連載更新,同步翻譯版鏈接。 歡迎關(guān)注我的專欄,之后的博文將在專欄同步: Enc...

    Seay 評論0 收藏0
  • JS與Node.js中的事件循環(huán)

    摘要:的單線程,與它的用途有關(guān)。特點的顯著特點異步機制事件驅(qū)動。隊列的讀取輪詢線程,事件的消費者,的主角。它將不同的任務(wù)分配給不同的線程,形成一個事件循環(huán),以異步的方式將任務(wù)的執(zhí)行結(jié)果返回給引擎。 這兩天跟同事同事討論遇到的一個問題,js中的event loop,引出了chrome與node中運行具有setTimeout和Promise的程序時候執(zhí)行結(jié)果不一樣的問題,從而引出了Nodejs的...

    abson 評論0 收藏0
  • Node.js 指南(不要阻塞事件循環(huán)或工作池)

    摘要:為什么要避免阻塞事件循環(huán)和工作池使用少量線程來處理許多客戶端,在中有兩種類型的線程一個事件循環(huán)又稱主循環(huán)主線程事件線程等,以及一個工作池也稱為線程池中的個的池。 不要阻塞事件循環(huán)(或工作池) 你應(yīng)該閱讀這本指南嗎? 如果你編寫的內(nèi)容比簡短的命令行腳本更復(fù)雜,那么閱讀本文應(yīng)該可以幫助你編寫性能更高、更安全的應(yīng)用程序。 本文檔是在考慮Node服務(wù)器的情況下編寫的,但這些概念也適用于復(fù)雜的N...

    hatlonely 評論0 收藏0
  • 不要阻塞事件循環(huán)(或工作池)

    摘要:接下來的部分將討論如何確保事件循環(huán)和工作池的公平調(diào)度。不要阻塞事件循環(huán)事件循環(huán)通知每個新客戶端連接并協(xié)調(diào)對客戶端的響應(yīng)。 你應(yīng)該閱讀本指南嗎? 如果您編寫比命令行腳本更復(fù)雜的程序,那么閱讀本文可以幫助您編寫性能更高,更安全的應(yīng)用程序。 在編寫本文檔時,主要是基于Node服務(wù)器。但里面的原則也適用于其它復(fù)雜的Node應(yīng)用程序。在沒有特別說明操作系統(tǒng)的情況下,默認為Linux。 TL; D...

    widuu 評論0 收藏0
  • Node_深入淺出Node

    摘要:簡介項目命名為就是一個服務(wù)器單純開發(fā)一個服務(wù)器的想法,變成構(gòu)建網(wǎng)絡(luò)應(yīng)用的一個基本框架發(fā)展為一個強制不共享任何資源的單線程,單進程系統(tǒng)。單線程弱點無法利用多核錯誤會引起整個應(yīng)用退出,應(yīng)用的健壯性大量計算占用導(dǎo)致無法繼續(xù)調(diào)用異步。 NodeJs簡介 Ryan Dahl項目命名為:web.js 就是一個Web服務(wù)器.單純開發(fā)一個Web服務(wù)器的想法,變成構(gòu)建網(wǎng)絡(luò)應(yīng)用的一個基本框架.Node發(fā)展...

    shinezejian 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<