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

資訊專欄INFORMATION COLUMN

由setTimeout和setImmediate執(zhí)行順序的隨機(jī)性窺探Node的事件循環(huán)機(jī)制

marek / 2489人閱讀

摘要:?jiǎn)栴}引入接觸過(guò)事件循環(huán)的同學(xué)大都會(huì)糾結(jié)一個(gè)點(diǎn),就是在中和執(zhí)行順序的隨機(jī)性。當(dāng)隊(duì)列被執(zhí)行完,或者執(zhí)行的回調(diào)數(shù)量達(dá)到上限后,事件循環(huán)才會(huì)進(jìn)入下一個(gè)階段。嵌套的在下一個(gè)事件循環(huán)的階段執(zhí)行回調(diào)輸出嵌套的。

問(wèn)題引入

接觸過(guò)事件循環(huán)的同學(xué)大都會(huì)糾結(jié)一個(gè)點(diǎn),就是在Node中setTimeoutsetImmediate執(zhí)行順序的隨機(jī)性。

比如說(shuō)下面這段代碼:

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

執(zhí)行的結(jié)果是這樣子的:

為什么會(huì)出現(xiàn)這種情況呢?別急,我們先往下看。

瀏覽器中事件循環(huán)模型

我們都知道,JavaScript是單線程的語(yǔ)言,對(duì)I/O的控制是通過(guò)異步來(lái)實(shí)現(xiàn)的,具體是通過(guò)“事件循環(huán)”機(jī)制來(lái)實(shí)現(xiàn)。

對(duì)于JavaScript中的單線程,指的是JavaScript執(zhí)行在單線程中,而內(nèi)部I/O任務(wù)其實(shí)是另有線程池來(lái)完成的。

在瀏覽器中,我們討論事件循環(huán),是以“從宏任務(wù)隊(duì)列中取一個(gè)任務(wù)執(zhí)行,再取出微任務(wù)隊(duì)列中的所有任務(wù)”來(lái)分析執(zhí)行代碼的。但是在Node環(huán)境中并不適用。具體的瀏覽器事件循環(huán)解析:傳送門

在Node中,事件循環(huán)的模型和瀏覽器相比大致相同,而最大的不同點(diǎn)在于Node中事件循環(huán)分不同的階段。具體我們下面會(huì)討論到。本文核心也在這里。

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

下面是事件循環(huán)不同階段的示意圖:

每個(gè)階段都有一個(gè)先進(jìn)先出的回調(diào)隊(duì)列要執(zhí)行。而每個(gè)階段都有自己的特殊之處。簡(jiǎn)單來(lái)說(shuō),就是當(dāng)事件循環(huán)進(jìn)入某個(gè)階段后,會(huì)執(zhí)行該階段特定的任意操作,然后才會(huì)執(zhí)行這個(gè)階段里的回調(diào)。當(dāng)隊(duì)列被執(zhí)行完,或者執(zhí)行的回調(diào)數(shù)量達(dá)到上限后,事件循環(huán)才會(huì)進(jìn)入下一個(gè)階段。

以下是各個(gè)階段詳情。

timers

一個(gè)timer指定一個(gè)下限時(shí)間而不是準(zhǔn)確時(shí)間,在達(dá)到這個(gè)下限時(shí)間后執(zhí)行回調(diào)。在指定的時(shí)間過(guò)后,timers會(huì)盡早的執(zhí)行回調(diào),但是系統(tǒng)調(diào)度或者其他回調(diào)的執(zhí)行可能會(huì)延遲它們。

從技術(shù)上來(lái)說(shuō),poll階段控制timers什么時(shí)候執(zhí)行,而執(zhí)行的具體位置在timers。

下限的時(shí)間有一個(gè)范圍:[1, 2147483647],如果設(shè)定的時(shí)間不在這個(gè)范圍,將被設(shè)置為1。

I/O callbacks

這個(gè)階段執(zhí)行一些系統(tǒng)操作的回調(diào),比如說(shuō)TCP連接發(fā)生錯(cuò)誤。

idle, prepare

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

poll

這是最復(fù)雜的一個(gè)階段。

poll階段有兩個(gè)主要的功能:一是執(zhí)行下限時(shí)間已經(jīng)達(dá)到的timers的回調(diào),一是處理poll隊(duì)列里的事件。

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

以下是Node官網(wǎng)的介紹:

筆者把官網(wǎng)陳述的情況以不同的條件分解,更加的清楚。(如果有誤,師請(qǐng)改正。)

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

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

poll隊(duì)列為空的時(shí)候,這里有兩種情況。

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

如果代碼沒有被設(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ì)列。

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

check

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

setImmediate()實(shí)際上是一個(gè)特殊的timer,跑在事件循環(huán)中的一個(gè)獨(dú)立的階段。它使用libuvAPI來(lái)設(shè)定在poll階段結(jié)束后立即執(zhí)行回調(diào)。

注:setImmediate()具有最高優(yōu)先級(jí),只要poll隊(duì)列為空,代碼被setImmediate(),無(wú)論是否有timers達(dá)到下限時(shí)間,setImmediate()的代碼都先執(zhí)行。

close callbacks

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

關(guān)于setTimeout和setImmediate

代碼重現(xiàn),我們會(huì)發(fā)現(xiàn)setTimeoutsetImmediate在Node環(huán)境下執(zhí)行是靠“隨緣法則”的。

比如說(shuō)下面這段代碼:

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

執(zhí)行的結(jié)果是這樣子的:

為什么會(huì)這樣子呢?

這里我們要根據(jù)前面的那個(gè)事件循環(huán)不同階段的圖解來(lái)說(shuō)明一下:

首先進(jìn)入的是timers階段,如果我們的機(jī)器性能一般,那么進(jìn)入timers階段,一毫秒已經(jīng)過(guò)去了(setTimeout(fn, 0)等價(jià)于setTimeout(fn, 1)),那么setTimeout的回調(diào)會(huì)首先執(zhí)行。

如果沒有到一毫秒,那么在timers階段的時(shí)候,下限時(shí)間沒到,setTimeout回調(diào)不執(zhí)行,事件循環(huán)來(lái)到了poll階段,這個(gè)時(shí)候隊(duì)列為空,此時(shí)有代碼被setImmediate(),于是先執(zhí)行了setImmediate()的回調(diào)函數(shù),之后在下一個(gè)事件循環(huán)再執(zhí)行setTimemout的回調(diào)函數(shù)。

而我們?cè)趫?zhí)行代碼的時(shí)候,進(jìn)入timers的時(shí)間延遲其實(shí)是隨機(jī)的,并不是確定的,所以會(huì)出現(xiàn)兩個(gè)函數(shù)執(zhí)行順序隨機(jī)的情況。

那我們?cè)賮?lái)看一段代碼:

var fs = require("fs")

fs.readFile(__filename, () => {
    setTimeout(() => {
        console.log("timeout");
    }, 0);
    setImmediate(() => {
        console.log("immediate");
    });
});

這里我們就會(huì)發(fā)現(xiàn),setImmediate永遠(yuǎn)先于setTimeout執(zhí)行。

原因如下:

fs.readFile的回調(diào)是在poll階段執(zhí)行的,當(dāng)其回調(diào)執(zhí)行完畢之后,poll隊(duì)列為空,而setTimeout入了timers的隊(duì)列,此時(shí)有代碼被setImmediate(),于是事件循環(huán)先進(jìn)入check階段執(zhí)行回調(diào),之后在下一個(gè)事件循環(huán)再在timers階段中執(zhí)行有效回調(diào)。

同樣的,這段代碼也是一樣的道理:

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

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

綜上,我們可以總結(jié):

如果兩者都在主模塊中調(diào)用,那么執(zhí)行先后取決于進(jìn)程性能,也就是隨機(jī)。

如果兩者都不在主模塊調(diào)用(被一個(gè)異步操作包裹),那么setImmediate的回調(diào)永遠(yuǎn)先執(zhí)行。

process.nextTick() and Promise

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

那么他們是在什么時(shí)候執(zhí)行呢?

不管在什么地方調(diào)用,他們都會(huì)在其所處的事件循環(huán)最后,事件循環(huán)進(jìn)入下一個(gè)循環(huán)的階段前執(zhí)行。

舉個(gè)?:

setTimeout(() => {
    console.log("timeout0");
    process.nextTick(() => {
        console.log("nextTick1");
        process.nextTick(() => {
            console.log("nextTick2");
        });
    });
    process.nextTick(() => {
        console.log("nextTick3");
    });
    console.log("sync");
    setTimeout(() => {
        console.log("timeout2");
    }, 0);
}, 0);

結(jié)果是:

再解釋一下:

timers階段執(zhí)行外層setTimeout的回調(diào),遇到同步代碼先執(zhí)行,也就有timeout0sync的輸出。遇到process.nextTick后入微任務(wù)隊(duì)列,依次nextTick1、nextTick3、nextTick2入隊(duì)后出隊(duì)輸出。之后,在下一個(gè)事件循環(huán)的timers階段,執(zhí)行setTimeout回調(diào)輸出timeout2

最后

下面給出兩段代碼,如果能夠理解其執(zhí)行順序說(shuō)明你已經(jīng)理解透徹。

代碼1:

setImmediate(function(){
  console.log("setImmediate");
  setImmediate(function(){
    console.log("嵌套setImmediate");
  });
  process.nextTick(function(){
    console.log("nextTick");
  })
});

// setImmediate
// nextTick
// 嵌套setImmediate

解析:事件循環(huán)check階段執(zhí)行回調(diào)函數(shù)輸出setImmediate,之后輸出nextTick。嵌套的setImmediate在下一個(gè)事件循環(huán)的check階段執(zhí)行回調(diào)輸出嵌套的setImmediate

代碼2:

var fs = require("fs");

function someAsyncOperation (callback) {
  // 假設(shè)這個(gè)任務(wù)要消耗 95ms
  fs.readFile("/path/to/file", callback);
}

var timeoutScheduled = Date.now();

setTimeout(function () {

  var delay = Date.now() - timeoutScheduled;

  console.log(delay + "ms have passed since I was scheduled");
}, 100);


// someAsyncOperation要消耗 95 ms 才能完成
someAsyncOperation(function () {

  var startCallback = Date.now();

  // 消耗 10ms...
  while (Date.now() - startCallback < 10) {
    ; // do nothing
  }

});

解析:事件循環(huán)進(jìn)入poll階段發(fā)現(xiàn)隊(duì)列為空,并且沒有代碼被setImmediate()。于是在poll階段等待timers下限時(shí)間到達(dá)。當(dāng)?shù)鹊?b>95ms時(shí),fs.readFile首先執(zhí)行了,它的回調(diào)被添加進(jìn)poll隊(duì)列并同步執(zhí)行,耗時(shí)10ms。此時(shí)總共時(shí)間累積105ms。等到poll隊(duì)列為空的時(shí)候,事件循環(huán)會(huì)查看最近到達(dá)的timer的下限時(shí)間,發(fā)現(xiàn)已經(jīng)到達(dá),再回到timers階段,執(zhí)行timer的回調(diào)。

如果有什么問(wèn)題,歡迎留言交流探討。

參考鏈接:

https://nodejs.org/en/docs/gu...

https://github.com/creeperyan...

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/107169.html

相關(guān)文章

  • Node事件循環(huán)異步API

    摘要:異步在中,是在單線程中執(zhí)行的沒錯(cuò),但是內(nèi)部完成工作的另有線程池,使用一個(gè)主進(jìn)程和多個(gè)線程來(lái)模擬異步。在事件循環(huán)中,觀察者會(huì)不斷的找到線程池中已經(jīng)完成的請(qǐng)求對(duì)象,從中取出回調(diào)函數(shù)和數(shù)據(jù)并執(zhí)行。 1. 介紹 單線程編程會(huì)因阻塞I/O導(dǎo)致硬件資源得不到更優(yōu)的使用。多線程編程也因?yàn)榫幊讨械乃梨i、狀態(tài)同步等問(wèn)題讓開發(fā)人員頭痛。Node在兩者之間給出了它的解決方案:利用單線程,遠(yuǎn)離多線程死鎖、狀態(tài)...

    atinosun 評(píng)論0 收藏0
  • JavaScript單線程事件循環(huán)(Event Loop)那些事

    摘要:概述本篇主要介紹的運(yùn)行機(jī)制單線程事件循環(huán)結(jié)論先在中利用運(yùn)行至完成和非阻塞完成單線程下異步任務(wù)的處理就是先處理主模塊主線程上的同步任務(wù)再處理異步任務(wù)異步任務(wù)使用事件循環(huán)機(jī)制完成調(diào)度涉及的內(nèi)容有單線程事件循環(huán)同步執(zhí)行異步執(zhí)行定時(shí)器的事件循環(huán)開始 1.概述 本篇主要介紹JavaScript的運(yùn)行機(jī)制:單線程事件循環(huán)(Event Loop). 結(jié)論先: 在JavaScript中, 利用運(yùn)行至...

    Shisui 評(píng)論0 收藏0
  • 瀏覽器與Node事件循環(huán)(Event Loop)有何區(qū)別?

    摘要:事件觸發(fā)線程主要負(fù)責(zé)將準(zhǔn)備好的事件交給引擎線程執(zhí)行。它將不同的任務(wù)分配給不同的線程,形成一個(gè)事件循環(huán),以異步的方式將任務(wù)的執(zhí)行結(jié)果返回給引擎。 Fundebug經(jīng)作者浪里行舟授權(quán)首發(fā),未經(jīng)同意請(qǐng)勿轉(zhuǎn)載。 前言 本文我們將會(huì)介紹 JS 實(shí)現(xiàn)異步的原理,并且了解了在瀏覽器和 Node 中 Event Loop 其實(shí)是不相同的。 一、線程與進(jìn)程 1. 概念 我們經(jīng)常說(shuō) JS 是單線程執(zhí)行的,...

    TANKING 評(píng)論0 收藏0
  • 用一道大廠面試題帶你搞懂事件循環(huán)機(jī)制

    本文涵蓋 面試題的引入 對(duì)事件循環(huán)面試題執(zhí)行順序的一些疑問(wèn) 通過(guò)面試題對(duì)微任務(wù)、事件循環(huán)、定時(shí)器等對(duì)深入理解 結(jié)論總結(jié) 面試題 面試題如下,大家可以先試著寫一下輸出結(jié)果,然后再看我下面的詳細(xì)講解,看看會(huì)不會(huì)有什么出入,如果把整個(gè)順序弄清楚 Node.js 的執(zhí)行順序應(yīng)該就沒問(wèn)題了。 async function async1(){ console.log(async1 start) ...

    ShowerSun 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<