摘要:前沿是基于引擎的運行環境具有事件驅動非阻塞等特點結合具有網絡編程文件系統等服務端的功能用庫進行異步事件處理線程的單線程含義實際上說的是執行同步代碼的主線程一個程序的啟動不止是分配了一個線程,而是我們只能在一個線程執行代碼當出現資源調用連接等
前沿
線程Node.js 是基于V8引擎的javascript運行環境. Node.js具有事件驅動, 非阻塞I/O等特點. 結合Node API, Node.js 具有網絡編程, 文件系統等服務端的功能, Node.js用libuv庫進行異步事件處理.
Node.js的單線程含義, 實際上說的是執行同步代碼的主線程. 一個Node程序的啟動, 不止是分配了一個線程,而是我們只能在一個線程執行代碼. 當出現I/O資源調用, TCP連接等外部資源申請的時候, 不會阻塞主線程, 而是委托給I/O線程進行處理,并且進入等待隊列. 一旦主線程執行完成,將會消費事件隊列(Event Queue). 因為只有一個主線程, 只占用CPU內核處理邏輯計算, 因此不適合在CPU密集型進行使用.
注意,上圖的EVENT_QUEUE 給人看起來是只有一個隊列, 根據Node.js官方介紹, EventLoop有6個階段, 同時每個階段都有對應的一個先進先出的回調隊列.
什么是事件循環(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 是一種常用的機制,通過對內部或外部的事件提供者發出請求, 如文件讀寫, 網絡連接 等異步操作, 完成后調用事件處理程序. 整個過程都是異步階段
Node.js的事件循環機制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
大致含義: 當Node.js 啟動, 就會初始化一個 event loop, 處理腳本時, 可能會發生異步API行為調用, 使用定時器任務或者nexTick, 處理完成后進入事件循環處理過程
事件循環階段┌───────────────────────┐ ┌─>│ timers │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ I/O callbacks │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ │ │ idle, prepare │ │ └──────────┬────────────┘ ┌───────────────┐ │ ┌──────────┴────────────┐ │ incoming: │ │ │ poll │<─────┤ connections, │ │ └──────────┬────────────┘ │ data, etc. │ │ ┌──────────┴────────────┐ └───────────────┘ │ │ check │ │ └──────────┬────────────┘ │ ┌──────────┴────────────┐ └──┤ close callbacks │ └───────────────────────┘
每一個階段都有一個FIFO的callbacks隊列, 每個階段都有自己的事件處理方式. 當事件循環進入某個階段時, 將會在該階段內執行回調,直到隊列耗盡或者回調的最大數量已執行, 那么將進入下一個處理階段.
timers 階段: 這個階段執行setTimeout(callback) and setInterval(callback)預定的callback;
I/O callbacks 階段: 執行除了close事件的callbacks、被timers(定時器,setTimeout、setInterval等)設定的callbacks、setImmediate()設定的callbacks之外的callbacks; (目前這個階段)
idle, prepare 階段: 僅node內部使用;
poll 階段: 獲取新的I/O事件, 適當的條件下node將阻塞在這里;
check 階段: 執行setImmediate() 設定的callbacks;
close callbacks 階段: 比如socket.on(‘close’, callback)的callback會在這個階段執行.
下面是摘抄creeperyang 對上面6個階段的 (原文翻譯)
timers階段一個timer指定一個下限時間而不是準確時間,在達到這個下限時間后執行回調。在指定時間過后,timers會盡可能早地執行回調,但系統調度或者其它回調的執行可能會延遲它們。
注意:技術上來說,poll 階段控制 timers 什么時候執行。
注意:這個下限時間有個范圍:[1, 2147483647],如果設定的時間不在這個范圍,將被設置為1。
I/O callbacks階段這個階段執行一些系統操作的回調。比如TCP錯誤,如一個TCP socket在想要連接時收到ECONNREFUSED,
類unix系統會等待以報告錯誤,這就會放到 I/O callbacks 階段的隊列執行.
名字會讓人誤解為執行I/O回調處理程序, 實際上I/O回調會由poll階段處理.
poll 階段有兩個主要功能:
執行下限時間已經達到的timers的回調,然后
處理 poll 隊列里的事件。
當event loop進入 poll 階段,并且 沒有設定的timers(there are no timers scheduled),會發生下面兩件事之一:
如果 poll 隊列不空,event loop會遍歷隊列并同步執行回調,直到隊列清空或執行的回調數到達系統上限;
如果 poll 隊列為空,則發生以下兩件事之一:
如果代碼已經被setImmediate()設定了回調, event loop將結束 poll 階段進入 check 階段來執行 check 隊列(里的回調)。
如果代碼沒有被setImmediate()設定回調,event loop將阻塞在該階段等待回調被加入 poll 隊列,并立即執行。
但是,當event loop進入 poll 階段,并且 有設定的timers,一旦 poll 隊列為空(poll 階段空閑狀態):
event loop將檢查timers,如果有1個或多個timers的下限時間已經到達,event loop將繞回 timers 階段,并執行 timer 隊列。
check階段這個階段允許在 poll 階段結束后立即執行回調。如果 poll 階段空閑,并且有被setImmediate()設定的回調,event loop會轉到 check 階段而不是繼續等待。
setImmediate()實際上是一個特殊的timer,跑在event loop中一個獨立的階段。它使用libuv的API
來設定在 poll 階段結束后立即執行回調。
通常上來講,隨著代碼執行,event loop終將進入 poll 階段,在這個階段等待 incoming connection, request 等等。但是,只要有被setImmediate()設定了回調,一旦 poll 階段空閑,那么程序將結束 poll 階段并進入 check 階段,而不是繼續等待 poll 事件們 (poll events)。
close callbacks 階段如果一個 socket 或 handle 被突然關掉(比如 socket.destroy()),close事件將在這個階段被觸發,否則將通過process.nextTick()觸發
簡單的 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(() => { // 進入event loop // timers階段之前執行 wait(20); asyncOperation(() => { console.log("poll"); }); }); /** * result: * timers 21ms * poll */
為了讓setTimeout優先于fs.readFile 回調, 執行了process.nextTick, 表示在進入 timers階段前, 等待20ms后執行文件讀取.
nextTick 與 setImmediateprocess.nextTick 不屬于事件循環的任何一個階段,它屬于該階段與下階段之間的過渡, 即本階段執行結束, 進入下一個階段前, 所要執行的回調。有給人一種插隊的感覺.
setImmediate的回調處于check階段, 當poll階段的隊列為空, 且check階段的事件隊列存在的時候,切換到check階段執行.
nextTick 遞歸的危害
由于nextTick具有插隊的機制,nextTick的遞歸會讓事件循環機制無法進入下一個階段. 導致I/O處理完成或者定時任務超時后仍然無法執行, 導致了其它事件處理程序處于饑餓狀態. 為了防止遞歸產生的問題, Node.js 提供了一個 process.maxTickDepth (默認 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();
此時永遠無法跳到timer階段, 因為在進入timers階段前有不斷的nextTick插入執行. 除非執行了1000次到了執行上限.
setImmediate
如果在一個I/O周期內進行調度,setImmediate()將始終在任何定時器之前執行.
setImmediate()被設計在 poll 階段結束后立即執行回調;
setTimeout()被設計在指定下限時間到達后執行回調;
無 I/O 處理情況下
setTimeout(function timeout () { console.log("timeout"); },0); setImmediate(function immediate () { console.log("immediate"); });
輸出結果是 不確定 的!
setTimeout(fn, 0) 具有幾毫秒的不確定性. 無法保證進入timers階段, 定時器能夠立即執行處理程序.
在I/O事件處理程序下
var fs = require("fs") fs.readFile(__filename, () => { setTimeout(() => { console.log("timeout") }, 0) setImmediate(() => { console.log("immediate") }) })
此時 setImmediate 優先于 setTimeout 執行,因為 poll階段執行完成后 進入 check階段. timers階段處于下一個事件循環階段了.
相關文章淺析 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()
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/90145.html
摘要:輪詢投票處理下一次處理的新事件立即設置運行通過注冊的所有回調關閉執行所有的回調工作處理延遲此度量標準測量線程池處理異步任務需要多長時間。高工作時間處理延遲表明繁忙耗盡的線程池。 原文=> What you should know to really understand the Node.js Event Loop Node.js 是一個基于事件的平臺。這就意味著在Node中發生的所...
摘要:概述本文主要介紹了我對的一些核心特性的理解,包括架構特點機制核心模塊與簡單應用。在此期間,主線程繼續執行其他任務。延續了瀏覽器端單線程,只用一個主線程執行,不斷循環遍歷事件隊列,執行事件。 原文地址在我的博客,轉載請注明來源,謝謝! node是在前端領域經常看到的詞。node對于前端的重要性已經不言而喻,掌握node也是作為合格的前端工程師一項基本功了。知道node、知道后端的一些東西...
摘要:如果當前沒有事件也沒有定時器事件,則返回。相關資料關于的架構及設計思路的事件討論了使用線程池異步運行代碼。下一篇初窺事件機制的實現二中定時器的實現 在瀏覽器中,事件作為一個極為重要的機制,給予JavaScript響應用戶操作與DOM變化的能力;在Node.js中,事件驅動模型則是其高并發能力的基礎。 學習JavaScript也需要了解它的運行平臺,為了更好的理解JavaScript的事...
本文涵蓋 面試題的引入 對事件循環面試題執行順序的一些疑問 通過面試題對微任務、事件循環、定時器等對深入理解 結論總結 面試題 面試題如下,大家可以先試著寫一下輸出結果,然后再看我下面的詳細講解,看看會不會有什么出入,如果把整個順序弄清楚 Node.js 的執行順序應該就沒問題了。 async function async1(){ console.log(async1 start) ...
摘要:一句話解釋在事件循環機制中,有任務兩個隊列隊列和隊列。設置任務為目前運行的任務,并執行。應該是考慮到了這一點,至少任務中的任務,是被設置了在一個事件循環中的最大調用次數的,叫。參考材料理解事件循環 在Node學習過程中,不可避免的需要對事件循環機制做深入理解,其中Macrotask(大型任務)和Microtask(小型任務)比較令人困惑,在一番google之后,我發現了幾篇資料能比較好...
閱讀 2470·2021-11-17 09:33
閱讀 757·2021-11-04 16:13
閱讀 1329·2021-10-14 09:50
閱讀 691·2019-08-30 15:53
閱讀 3657·2019-08-30 14:18
閱讀 3268·2019-08-30 14:14
閱讀 2093·2019-08-30 12:46
閱讀 3178·2019-08-26 14:05