成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Node中的事件循環(huán)

lwx12525 / 1656人閱讀

摘要:的事件循環(huán)一個(gè)線程有唯一的一個(gè)事件循環(huán)。索引就是指否還有需要執(zhí)行的事件,是否還有請(qǐng)求,關(guān)閉事件循環(huán)的請(qǐng)求等等。先來看一下定義的定義是在事件循環(huán)的下一個(gè)階段之前執(zhí)行對(duì)應(yīng)的回調(diào)。雖然是這樣定義的,但是它并不是為了在事件循環(huán)的每個(gè)階段去執(zhí)行的。

Node中的事件循環(huán)

如果對(duì)前端瀏覽器的時(shí)間循環(huán)不太清楚,請(qǐng)看這篇文章。那么node中的事件循環(huán)是什么樣子呢?其實(shí)官方文檔有很清楚的解釋,本文先從node執(zhí)行一個(gè)單文件說起,再講事件循環(huán)。

node的內(nèi)部模塊

任何高級(jí)語言的存在都有一定的執(zhí)行環(huán)境,比如瀏覽器的代碼是在瀏覽器引擎中,那么在node環(huán)境中也有一定的執(zhí)行環(huán)境。我們先來看一下官網(wǎng)的依賴包有哪些?

V8

libuv

http-parser

c-cares

OpenSSL

zlib

上面就是nodejs中依賴的模塊。那么這些模塊之間是如何工作的呢?模塊之間的工作關(guān)系如下圖所示:


主要過程如下:

step1: 用戶的代碼通過v8引擎解釋器,解析為兩部分:"立即執(zhí)行"和"異步執(zhí)行"。

立即執(zhí)行:可以理解為,需要v8引擎去處理的代碼;
異步執(zhí)行:并不是真正的異步,可以理解為,不需要v8引擎處理的和需要異步處理的。

step2: “異步執(zhí)行”的部分,通過v8引擎和底層之間建立的綁定關(guān)系,去執(zhí)行對(duì)應(yīng)的操作

step3: 在“異步執(zhí)行”部分,通過libuv內(nèi)部的事件循環(huán)機(jī)制,無阻塞調(diào)用。libuv在執(zhí)行的時(shí)候,主要通過handles和request實(shí)現(xiàn)對(duì)應(yīng)的操作,handles和requests具備不同的數(shù)據(jù)結(jié)構(gòu)。官網(wǎng)解釋,handles是長(zhǎng)期存在的對(duì)象,request是短期存在的對(duì)象,猜測(cè)來講,requests和handles有不同的垃圾回收機(jī)制。

libuv的事件循環(huán)
一個(gè)線程有唯一的一個(gè)事件循環(huán)(event loop)。線程非安全。

這里需要理解兩點(diǎn):

線程

這可能和我們理解的不太一樣,Javascript代碼是單線程的,但是libuv不是單線程的,他可以開啟多個(gè)線程,libuv 提供了一個(gè)調(diào)度的線程池,線程池中的線程數(shù)目,默認(rèn)是4個(gè),最多1024個(gè)(為什么?因?yàn)槊恳粋€(gè)線程都會(huì)占用資源,而內(nèi)存是有限的),關(guān)于線程池的可以看官方文檔。

線程安全

對(duì)數(shù)據(jù)的操作無非就是讀和寫,線程安全,簡(jiǎn)單來說,就是一個(gè)線程對(duì)這一份數(shù)據(jù)具有獨(dú)占性,只有當(dāng)該線程操作完成,其他線程才可以進(jìn)行操作,當(dāng)然線程安全的概念遠(yuǎn)不止這些,詳細(xì)可以看維基百科,這里就簡(jiǎn)單理解一下就行了。

libuv中的事件循環(huán)

事件循環(huán)圖,如下所示:

主要分為下面幾步:

step1: 線程啟動(dòng)時(shí),初始化一個(gè)時(shí)間:now,為了計(jì)算后面的timer的回調(diào)函數(shù)什么時(shí)候執(zhí)行

step2: 判斷事件循環(huán)是否存活,如果不存活,立即退出,否則進(jìn)行下一步。判斷是否存活的依據(jù):索引是否存在。索引就是指否還有需要執(zhí)行的事件,是否還有請(qǐng)求,關(guān)閉事件循環(huán)的請(qǐng)求等等。(用白話來講,就是看還有沒有沒處理的事情)

step3: 執(zhí)行所有的定時(shí)器(timers)在事件循環(huán)之前

step4: 執(zhí)行待執(zhí)行(pending)的回調(diào),一般的IO輪詢都會(huì)在輪詢后,立即執(zhí)行,但是有的也會(huì)延遲(defer)執(zhí)行,延遲執(zhí)行的,就會(huì)在這個(gè)階段執(zhí)行

step4: 執(zhí)行空閑(idle)函數(shù),每個(gè)階段都會(huì)執(zhí)行的,一般情況下是執(zhí)行一些必要的操作,程序內(nèi)置的

step5: 執(zhí)行準(zhǔn)備好的回調(diào)函數(shù),具體內(nèi)部使用的

step6: IO輪詢執(zhí)行,直到超時(shí),在阻塞執(zhí)行之前,會(huì)計(jì)算超時(shí)時(shí)間,也就是停止輪詢的時(shí)間:

如果隊(duì)列為空、或者是即將關(guān)閉,或者有將要關(guān)閉的handles,timeout為0

如果沒有上面的情況,超時(shí)時(shí)間就取最近的timer時(shí)間,否則就是無窮大

(用白話來理解,就是看有沒有要關(guān)閉的,有的話,就直接往下走,沒有的話,看看有哪個(gè)事件比較急,到了點(diǎn)就去執(zhí)行)

step7: 執(zhí)行IO

step8: 檢查接下來要執(zhí)行哪些handle,保證正確執(zhí)行

step9: 是否存在關(guān)閉的回調(diào),如果有就執(zhí)行,關(guān)閉循環(huán),否則繼續(xù)循環(huán)

通常情況下來講,文件的I/O會(huì)調(diào)用線程池,但是網(wǎng)絡(luò)請(qǐng)求的I/O總是用同一個(gè)線程。

Node中的事件循環(huán) 阻塞和非阻塞

node中所有的代碼幾乎都提供了同步(阻塞)和異步(非阻塞)的方式,你可以選擇使用哪一種方式,但是不要混合使用。

node中的事件循環(huán),就是一個(gè)簡(jiǎn)版的libuv事件循環(huán)機(jī)制圖

NodeJs中的定時(shí)器

NodeJs中的定時(shí)器主要有三種:

setTimeout

setInterval

setImmediate

三個(gè)定時(shí)器都有對(duì)應(yīng)的取消函數(shù):

clearTimeout

clearInterval

clearImmediate

setTimeout && setInterval

setTimeout和setInterval行為和在瀏覽器環(huán)境中的行為類似,但是setTimeout和setImmediate有一點(diǎn)不同。在libuv中可以看到,判斷循環(huán)是否結(jié)束的時(shí)候,是需要判斷是否還有待執(zhí)行的函數(shù),如果只剩下一個(gè)setTimeout或者setInterval函數(shù),那么整個(gè)循環(huán)還會(huì)繼續(xù)存在,node提供了一個(gè)函數(shù),可以讓循環(huán)暫時(shí)休眠。

unref

ref

unref是可以讓setTimeout暫時(shí)休眠,ref可以再次喚醒

setImmediate

setImmediate是指定在事件循環(huán)結(jié)束執(zhí)行的。主要發(fā)生在poll階段之后

如果poll隊(duì)列沒空,則一直執(zhí)行,直到對(duì)列空位置

如果poll隊(duì)列空了,有setImmediate事件,則會(huì)跳到check階段

如果poll隊(duì)列空了,沒有setImmediate事件,就會(huì)查看哪一個(gè)timer事件快要到期了,轉(zhuǎn)到timers階段

依據(jù)上面的解釋,就有了setTimeout和setImmediate執(zhí)行先后順序的問題:

setTimeout(() => {
  console.log("timeout");
})
setImmediate(() => {
  console.log("immediate);
});

先說答案:

可能會(huì)有兩種情況:
timeout
immediate
或者
immediate
timeout

為什么?
主要是setTimeout在前或者后的問題,依賴于線程的執(zhí)行速度。
主要是兩個(gè)階段:

1、v8引擎執(zhí)行環(huán)境掃描代碼,啟動(dòng)事件循環(huán),當(dāng)走到setTimeout的時(shí)候,會(huì)將timeout丟進(jìn)libuv事件隊(duì)列中

2、v8引擎繼續(xù)執(zhí)行,走到setImmediate

此時(shí),上面的libuv事件隊(duì)列可能執(zhí)行第一次,剛走到poll階段,那么接下來就會(huì)打印immediate,

也可能libuv事件隊(duì)列,已經(jīng)第二次循環(huán),經(jīng)過了poll階段,然后判斷timeout到時(shí)間了,去執(zhí)行timeout了,這樣就會(huì)先打印timeout然后再打印immediate

所以根本原因是在于事件循環(huán)執(zhí)行了一次還是兩次。

那我們接下來看看事件循環(huán)的邏輯

nextTick

Node添加了這樣一個(gè)API,這個(gè)并不在事件循環(huán)的機(jī)制內(nèi),但是和時(shí)間循環(huán)機(jī)制相關(guān)。先來看一下定義:

nextTick的定義是在事件循環(huán)的下一個(gè)階段之前執(zhí)行對(duì)應(yīng)的回調(diào)。

雖然nextTick是這樣定義的,但是它并不是為了在事件循環(huán)的每個(gè)階段去執(zhí)行的。
主要有下面兩種應(yīng)用場(chǎng)景:

作為下一個(gè)執(zhí)行階段的鉤子,去清理不需要的資源,或者再次請(qǐng)求

等運(yùn)行環(huán)境準(zhǔn)備好之后,再去執(zhí)行回調(diào)

案例一:
let bar;

function someAsyncApiCall(callback) {
  callback()
  process.nextTick(callback);
}

someAsyncApiCall(() => {
  console.log("bar", bar); // 1
});

bar = 1;

// 輸出
undefined
1

輸出undefine的情況是,因?yàn)閳?zhí)行函數(shù)的時(shí)候,bar并沒有被賦值,而process.nextTick則能保證整個(gè)執(zhí)行環(huán)境都準(zhǔn)備好了再去執(zhí)行

案例二:
const server = net.createServer();
server.on("connection", (conn) => { });

server.listen(8080);
server.on("listening", () => { });

當(dāng)v8引擎執(zhí)行完代碼后,listen的回調(diào)會(huì)直接命中poll階段,那么server的connect事件就不會(huì)執(zhí)行

案例三:

想要在構(gòu)造函數(shù)中,去發(fā)送對(duì)應(yīng)的事件,因?yàn)榇藭r(shí)v8引擎還沒有掃描到,而構(gòu)造函數(shù)的代碼會(huì)立即執(zhí)行,就需要nextTick

const EventEmitter = require("events");
const util = require("util");

function MyEmitter() {
  EventEmitter.call(this);
  // 這樣操作無效
  this.emit("event");
  // 應(yīng)該這樣
  // process.nextTick(() => {
    this.emit("event");
  });
}
util.inherits(MyEmitter, EventEmitter);

const myEmitter = new MyEmitter();
myEmitter.on("event", () => {
  console.log("an event occurred!");
});
總結(jié)

上面三個(gè)案例,重點(diǎn)在于v8引擎是單線程立即執(zhí)行,而libuv則是異步執(zhí)行,想要在異步循環(huán)之前執(zhí)行一些操作就需要process.nextTick

參考文檔

Node官網(wǎng)解釋
libuv的設(shè)計(jì)
關(guān)于libuv的概念詳細(xì)解釋
libuv線程池實(shí)現(xiàn)
并發(fā)

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/106971.html

相關(guān)文章

  • [譯]事件循環(huán)Node.js背后的核心概念

    摘要:事件處理器,則是當(dāng)指定事件觸發(fā)時(shí),執(zhí)行的一段代碼。事件循環(huán)以一個(gè)無限循環(huán)的形式啟動(dòng),存在于二進(jìn)制文件里函數(shù)的最后,當(dāng)沒有更多可被執(zhí)行的事件處理器時(shí),它就退出。 前言 如果你了解過Node.js,那么你一定聽說過事件循環(huán)。你一定想知道它為什么那么特殊,并且為什么你需要關(guān)注它?此時(shí)此刻的你,可能已經(jīng)寫過許多基于Express.js的后端代碼,但沒有接觸到任何的循環(huán)。 在下文中,我們會(huì)先在一...

    Meils 評(píng)論0 收藏0
  • 瀏覽器和Node不同的事件循環(huán)(Event Loop)

    摘要:瀏覽器中與中事件循環(huán)與執(zhí)行機(jī)制不同,不可混為一談。瀏覽器環(huán)境執(zhí)行為單線程不考慮,所有代碼皆在執(zhí)行線程調(diào)用棧完成執(zhí)行。參考文章強(qiáng)烈推薦不要混淆和瀏覽器中的強(qiáng)烈推薦中的模塊強(qiáng)烈推薦理解事件循環(huán)一淺析定時(shí)器詳解 注意 在 node 11 版本中,node 下 Event Loop 已經(jīng)與瀏覽器趨于相同。在 node 11 版本中,node 下 Event Loop 已經(jīng)與瀏覽器趨于相同。在 ...

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

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

    hatlonely 評(píng)論0 收藏0
  • Node中的事件循環(huán)和異步API

    摘要:異步在中,是在單線程中執(zhí)行的沒錯(cuò),但是內(nèi)部完成工作的另有線程池,使用一個(gè)主進(jìn)程和多個(gè)線程來模擬異步。在事件循環(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)同步等問題讓開發(fā)人員頭痛。Node在兩者之間給出了它的解決方案:利用單線程,遠(yuǎn)離多線程死鎖、狀態(tài)...

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

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

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

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

0條評(píng)論

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