摘要:前沿是基于引擎的運(yùn)行環(huán)境具有事件驅(qū)動(dòng)非阻塞等特點(diǎn)結(jié)合具有網(wǎng)絡(luò)編程文件系統(tǒng)等服務(wù)端的功能用庫(kù)進(jìn)行異步事件處理線程的單線程含義實(shí)際上說(shuō)的是執(zhí)行同步代碼的主線程一個(gè)程序的啟動(dòng)不止是分配了一個(gè)線程,而是我們只能在一個(gè)線程執(zhí)行代碼當(dāng)出現(xiàn)資源調(diào)用連接等
前沿
線程Node.js 是基于V8引擎的javascript運(yùn)行環(huán)境. Node.js具有事件驅(qū)動(dòng), 非阻塞I/O等特點(diǎn). 結(jié)合Node API, Node.js 具有網(wǎng)絡(luò)編程, 文件系統(tǒng)等服務(wù)端的功能, Node.js用libuv庫(kù)進(jìn)行異步事件處理.
Node.js的單線程含義, 實(shí)際上說(shuō)的是執(zhí)行同步代碼的主線程. 一個(gè)Node程序的啟動(dòng), 不止是分配了一個(gè)線程,而是我們只能在一個(gè)線程執(zhí)行代碼. 當(dāng)出現(xiàn)I/O資源調(diào)用, TCP連接等外部資源申請(qǐng)的時(shí)候, 不會(huì)阻塞主線程, 而是委托給I/O線程進(jìn)行處理,并且進(jìn)入等待隊(duì)列. 一旦主線程執(zhí)行完成,將會(huì)消費(fèi)事件隊(duì)列(Event Queue). 因?yàn)橹挥幸粋€(gè)主線程, 只占用CPU內(nèi)核處理邏輯計(jì)算, 因此不適合在CPU密集型進(jìn)行使用.
注意,上圖的EVENT_QUEUE 給人看起來(lái)是只有一個(gè)隊(duì)列, 根據(jù)Node.js官方介紹, EventLoop有6個(gè)階段, 同時(shí)每個(gè)階段都有對(duì)應(yīng)的一個(gè)先進(jìn)先出的回調(diào)隊(duì)列.
什么是事件循環(huán)(EventLoop) ?In computer science, the event loop, message dispatcher, message loop, message pump, or run loop is a programming construct that waits for and dispatches events or messages in a program. -- from wiki
大概含義: EventLoop 是一種常用的機(jī)制,通過(guò)對(duì)內(nèi)部或外部的事件提供者發(fā)出請(qǐng)求, 如文件讀寫, 網(wǎng)絡(luò)連接 等異步操作, 完成后調(diào)用事件處理程序. 整個(gè)過(guò)程都是異步階段
Node.js的事件循環(huán)機(jī)制When Node.js starts, it initializes the event loop, processes the provided input script (or drops into the REPL, which is not covered in this document) which may make async API calls, schedule timers, or call process.nextTick(), then begins processing the event loop. -- from node.js doc
大致含義: 當(dāng)Node.js 啟動(dòng), 就會(huì)初始化一個(gè) event loop, 處理腳本時(shí), 可能會(huì)發(fā)生異步API行為調(diào)用, 使用定時(shí)器任務(wù)或者nexTick, 處理完成后進(jìn)入事件循環(huán)處理過(guò)程
事件循環(huán)階段┌───────────────────────┐ ┌─>│ timers │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ I/O callbacks │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ idle, prepare │ │ └──────────┬────────────┘ ┌───────────────┐ │ ┌──────────┴────────────┐ │ incoming: │ │ │ poll │<─────┤ connections, │ │ └──────────┬────────────┘ │ data, etc. │ │ ┌──────────┴────────────┐ └───────────────┘ │ │ check │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ └──┤ close callbacks │ └───────────────────────┘
每一個(gè)階段都有一個(gè)FIFO的callbacks隊(duì)列, 每個(gè)階段都有自己的事件處理方式. 當(dāng)事件循環(huán)進(jìn)入某個(gè)階段時(shí), 將會(huì)在該階段內(nèi)執(zhí)行回調(diào),直到隊(duì)列耗盡或者回調(diào)的最大數(shù)量已執(zhí)行, 那么將進(jìn)入下一個(gè)處理階段.
timers 階段: 這個(gè)階段執(zhí)行setTimeout(callback) and setInterval(callback)預(yù)定的callback;
I/O callbacks 階段: 執(zhí)行除了close事件的callbacks、被timers(定時(shí)器,setTimeout、setInterval等)設(shè)定的callbacks、setImmediate()設(shè)定的callbacks之外的callbacks; (目前這個(gè)階段)
idle, prepare 階段: 僅node內(nèi)部使用;
poll 階段: 獲取新的I/O事件, 適當(dāng)?shù)臈l件下node將阻塞在這里;
check 階段: 執(zhí)行setImmediate() 設(shè)定的callbacks;
close callbacks 階段: 比如socket.on(‘close’, callback)的callback會(huì)在這個(gè)階段執(zhí)行.
下面是摘抄creeperyang 對(duì)上面6個(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í)行。
注意:這個(gè)下限時(shí)間有個(gè)范圍:[1, 2147483647],如果設(shè)定的時(shí)間不在這個(gè)范圍,將被設(shè)置為1。
I/O callbacks階段這個(gè)階段執(zhí)行一些系統(tǒng)操作的回調(diào)。比如TCP錯(cuò)誤,如一個(gè)TCP socket在想要連接時(shí)收到ECONNREFUSED,
類unix系統(tǒng)會(huì)等待以報(bào)告錯(cuò)誤,這就會(huì)放到 I/O callbacks 階段的隊(duì)列執(zhí)行.
名字會(huì)讓人誤解為執(zhí)行I/O回調(diào)處理程序, 實(shí)際上I/O回調(diào)會(huì)由poll階段處理.
poll 階段有兩個(gè)主要功能:
執(zhí)行下限時(shí)間已經(jīng)達(dá)到的timers的回調(diào),然后
處理 poll 隊(duì)列里的事件。
當(dāng)event loop進(jìn)入 poll 階段,并且 沒(méi)有設(shè)定的timers(there are no timers scheduled),會(huì)發(fā)生下面兩件事之一:
如果 poll 隊(duì)列不空,event loop會(huì)遍歷隊(duì)列并同步執(zhí)行回調(diào),直到隊(duì)列清空或執(zhí)行的回調(diào)數(shù)到達(dá)系統(tǒng)上限;
如果 poll 隊(duì)列為空,則發(fā)生以下兩件事之一:
如果代碼已經(jīng)被setImmediate()設(shè)定了回調(diào), event loop將結(jié)束 poll 階段進(jìn)入 check 階段來(lái)執(zhí)行 check 隊(duì)列(里的回調(diào))。
如果代碼沒(méi)有被setImmediate()設(shè)定回調(diào),event loop將阻塞在該階段等待回調(diào)被加入 poll 隊(duì)列,并立即執(zhí)行。
但是,當(dāng)event loop進(jìn)入 poll 階段,并且 有設(shè)定的timers,一旦 poll 隊(duì)列為空(poll 階段空閑狀態(tài)):
event loop將檢查timers,如果有1個(gè)或多個(gè)timers的下限時(shí)間已經(jīng)到達(dá),event loop將繞回 timers 階段,并執(zhí)行 timer 隊(duì)列。
check階段這個(gè)階段允許在 poll 階段結(jié)束后立即執(zhí)行回調(diào)。如果 poll 階段空閑,并且有被setImmediate()設(shè)定的回調(diào),event loop會(huì)轉(zhuǎn)到 check 階段而不是繼續(xù)等待。
setImmediate()實(shí)際上是一個(gè)特殊的timer,跑在event loop中一個(gè)獨(dú)立的階段。它使用libuv的API
來(lái)設(shè)定在 poll 階段結(jié)束后立即執(zhí)行回調(diào)。
通常上來(lái)講,隨著代碼執(zhí)行,event loop終將進(jìn)入 poll 階段,在這個(gè)階段等待 incoming connection, request 等等。但是,只要有被setImmediate()設(shè)定了回調(diào),一旦 poll 階段空閑,那么程序?qū)⒔Y(jié)束 poll 階段并進(jìn)入 check 階段,而不是繼續(xù)等待 poll 事件們 (poll events)。
close callbacks 階段如果一個(gè) socket 或 handle 被突然關(guān)掉(比如 socket.destroy()),close事件將在這個(gè)階段被觸發(fā),否則將通過(guò)process.nextTick()觸發(fā)
簡(jiǎn)單的 EventLoop
const fs = require("fs"); let counts = 0; function wait (mstime) { let date = Date.now(); while (Date.now() - date < mstime) { // do nothing } } function asyncOperation (callback) { fs.readFile(__dirname + "/" + __filename, callback); } const lastTime = Date.now(); setTimeout(() => { console.log("timers", Date.now() - lastTime + "ms"); }, 0); process.nextTick(() => { // 進(jìn)入event loop // timers階段之前執(zhí)行 wait(20); asyncOperation(() => { console.log("poll"); }); }); /** * result: * timers 21ms * poll */
為了讓setTimeout優(yōu)先于fs.readFile 回調(diào), 執(zhí)行了process.nextTick, 表示在進(jìn)入 timers階段前, 等待20ms后執(zhí)行文件讀取.
nextTick 與 setImmediateprocess.nextTick 不屬于事件循環(huán)的任何一個(gè)階段,它屬于該階段與下階段之間的過(guò)渡, 即本階段執(zhí)行結(jié)束, 進(jìn)入下一個(gè)階段前, 所要執(zhí)行的回調(diào)。有給人一種插隊(duì)的感覺(jué).
setImmediate的回調(diào)處于check階段, 當(dāng)poll階段的隊(duì)列為空, 且check階段的事件隊(duì)列存在的時(shí)候,切換到check階段執(zhí)行.
nextTick 遞歸的危害
由于nextTick具有插隊(duì)的機(jī)制,nextTick的遞歸會(huì)讓事件循環(huán)機(jī)制無(wú)法進(jìn)入下一個(gè)階段. 導(dǎo)致I/O處理完成或者定時(shí)任務(wù)超時(shí)后仍然無(wú)法執(zhí)行, 導(dǎo)致了其它事件處理程序處于饑餓狀態(tài). 為了防止遞歸產(chǎn)生的問(wèn)題, Node.js 提供了一個(gè) process.maxTickDepth (默認(rèn) 1000)。
遞歸nextTick
const fs = require("fs"); let counts = 0; function wait (mstime) { let date = Date.now(); while (Date.now() - date < mstime) { // do nothing } } function nextTick () { process.nextTick(() => { wait(20); nextTick(); }); } const lastTime = Date.now(); setTimeout(() => { console.log("timers", Date.now() - lastTime + "ms"); }, 0); nextTick();
此時(shí)永遠(yuǎn)無(wú)法跳到timer階段, 因?yàn)樵谶M(jìn)入timers階段前有不斷的nextTick插入執(zhí)行. 除非執(zhí)行了1000次到了執(zhí)行上限.
setImmediate
如果在一個(gè)I/O周期內(nèi)進(jìn)行調(diào)度,setImmediate()將始終在任何定時(shí)器之前執(zhí)行.
setImmediate()被設(shè)計(jì)在 poll 階段結(jié)束后立即執(zhí)行回調(diào);
setTimeout()被設(shè)計(jì)在指定下限時(shí)間到達(dá)后執(zhí)行回調(diào);
無(wú) I/O 處理情況下
setTimeout(function timeout () { console.log("timeout"); },0); setImmediate(function immediate () { console.log("immediate"); });
輸出結(jié)果是 不確定 的!
setTimeout(fn, 0) 具有幾毫秒的不確定性. 無(wú)法保證進(jìn)入timers階段, 定時(shí)器能夠立即執(zhí)行處理程序.
在I/O事件處理程序下
var fs = require("fs") fs.readFile(__filename, () => { setTimeout(() => { console.log("timeout") }, 0) setImmediate(() => { console.log("immediate") }) })
此時(shí) setImmediate 優(yōu)先于 setTimeout 執(zhí)行,因?yàn)?poll階段執(zhí)行完成后 進(jìn)入 check階段. timers階段處于下一個(gè)事件循環(huán)階段了.
相關(guān)文章淺析 Node.js 單線程模型
Node.js Event Loop 的理解 Timers,process.nextTick()
Node.js的event loop及timer/setImmediate/nextTick
The Node.js Event Loop, Timers, and process.nextTick()
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/90145.html
摘要:輪詢投票處理下一次處理的新事件立即設(shè)置運(yùn)行通過(guò)注冊(cè)的所有回調(diào)關(guān)閉執(zhí)行所有的回調(diào)工作處理延遲此度量標(biāo)準(zhǔn)測(cè)量線程池處理異步任務(wù)需要多長(zhǎng)時(shí)間。高工作時(shí)間處理延遲表明繁忙耗盡的線程池。 原文=> What you should know to really understand the Node.js Event Loop Node.js 是一個(gè)基于事件的平臺(tái)。這就意味著在Node中發(fā)生的所...
摘要:概述本文主要介紹了我對(duì)的一些核心特性的理解,包括架構(gòu)特點(diǎn)機(jī)制核心模塊與簡(jiǎn)單應(yīng)用。在此期間,主線程繼續(xù)執(zhí)行其他任務(wù)。延續(xù)了瀏覽器端單線程,只用一個(gè)主線程執(zhí)行,不斷循環(huán)遍歷事件隊(duì)列,執(zhí)行事件。 原文地址在我的博客,轉(zhuǎn)載請(qǐng)注明來(lái)源,謝謝! node是在前端領(lǐng)域經(jīng)??吹降脑~。node對(duì)于前端的重要性已經(jīng)不言而喻,掌握node也是作為合格的前端工程師一項(xiàng)基本功了。知道node、知道后端的一些東西...
摘要:如果當(dāng)前沒(méi)有事件也沒(méi)有定時(shí)器事件,則返回。相關(guān)資料關(guān)于的架構(gòu)及設(shè)計(jì)思路的事件討論了使用線程池異步運(yùn)行代碼。下一篇初窺事件機(jī)制的實(shí)現(xiàn)二中定時(shí)器的實(shí)現(xiàn) 在瀏覽器中,事件作為一個(gè)極為重要的機(jī)制,給予JavaScript響應(yīng)用戶操作與DOM變化的能力;在Node.js中,事件驅(qū)動(dòng)模型則是其高并發(fā)能力的基礎(chǔ)。 學(xué)習(xí)JavaScript也需要了解它的運(yùn)行平臺(tái),為了更好的理解JavaScript的事...
本文涵蓋 面試題的引入 對(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)該就沒(méi)問(wèn)題了。 async function async1(){ console.log(async1 start) ...
摘要:一句話解釋在事件循環(huán)機(jī)制中,有任務(wù)兩個(gè)隊(duì)列隊(duì)列和隊(duì)列。設(shè)置任務(wù)為目前運(yùn)行的任務(wù),并執(zhí)行。應(yīng)該是考慮到了這一點(diǎn),至少任務(wù)中的任務(wù),是被設(shè)置了在一個(gè)事件循環(huán)中的最大調(diào)用次數(shù)的,叫。參考材料理解事件循環(huán) 在Node學(xué)習(xí)過(guò)程中,不可避免的需要對(duì)事件循環(huán)機(jī)制做深入理解,其中Macrotask(大型任務(wù))和Microtask(小型任務(wù))比較令人困惑,在一番google之后,我發(fā)現(xiàn)了幾篇資料能比較好...
閱讀 2485·2021-11-17 09:33
閱讀 772·2021-11-04 16:13
閱讀 1345·2021-10-14 09:50
閱讀 706·2019-08-30 15:53
閱讀 3675·2019-08-30 14:18
閱讀 3277·2019-08-30 14:14
閱讀 2112·2019-08-30 12:46
閱讀 3192·2019-08-26 14:05