国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

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

atinosun / 1679人閱讀

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

1. 介紹

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

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

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

1.1 異步I/O

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

1.2 事件循環(huán)

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

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

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

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

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

主線程不斷重復上面的第三步。

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

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

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

Phases Overview 階段總覽

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

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

idle, prepare: 僅內部使用。

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

check: 執(zhí)行setImmediate()設定的回調。

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

1. timers

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

2. I/O callbacks

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

3. idle, prepare

系統(tǒng)內部的一些調用。

4. poll

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

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

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

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

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

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

如果代碼沒有被設定setImmediate()設定回調:

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

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

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

5. check

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

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

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

6. close callbacks

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

1.3 請求對象

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

1.4 執(zhí)行回調

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

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

2. 非I/O的異步API

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

2.1 setTimeout()setInterval()

這兩個方法實現(xiàn)原理與異步I/O相似,只不過不用I/O線程池的參與。
使用它們創(chuàng)建的定時器會被放入timers隊列的一個紅黑樹中,每次事件循環(huán)執(zhí)行時會從相應隊列中取出并判斷是否超過定時時間,超過就形成一個事件,回調立即執(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
  }

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

2.2 setImmediate()

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

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

setTimeout與setImmediate先后入隊之后,首先進入的是timers階段,如果我們的機器性能一般或者加入了一個同步長耗時操作,那么進入timers階段,1ms已經過去了,那么setTimeout的回調會首先執(zhí)行。
如果沒有到1ms,那么在timers階段的時候,超時時間沒到,setTimeout回調不執(zhí)行,事件循環(huán)來到了poll階段,這個時候隊列為空,此時有代碼被setImmediate(),于是先執(zhí)行了setImmediate()的回調函數(shù),之后在下一個事件循環(huán)再執(zhí)行setTimemout的回調函數(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的回調是在poll階段執(zhí)行的,當其回調執(zhí)行完畢之后,setTimeout與setImmediate先后入了timerscheck的隊列,繼續(xù)到pollpoll隊列為空,此時發(fā)現(xiàn)有setImmediate,于是事件循環(huán)先進入check階段執(zhí)行回調,之后在下一個事件循環(huán)再在timers階段中執(zhí)行setTimeout回調,雖然這個setTimeout已經到了超時時間。

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

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

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

2.3 process.nextTick()Promise

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

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

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

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

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

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 結論

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

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

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

網上的帖子大多深淺不一,甚至有些前后矛盾,在下的文章都是學習過程中的總結,如果發(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ī)行為,您可以聯(lián)系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/90536.html

相關文章

  • Node.js設計模式》Node.js基本模式

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

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

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

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

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

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

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

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

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

    shinezejian 評論0 收藏0

發(fā)表評論

0條評論

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