nodejs?快速入門之事件循環(huán)
瀏覽器中的事件循環(huán)
請?jiān)跒g覽器中運(yùn)行這段代碼:
console.log('1'); setTimeout(() => { console.log('2'); Promise.resolve().then(() => console.log('3')); Promise.resolve().then(() => console.log('4')); }, 100); setTimeout(() => { console.log('5'); Promise.resolve().then(() => console.log('6')); }, 150); Promise.resolve().then(() => console.log('7')); setTimeout(() => console.log('8'), 200); console.log('9'); /* 結(jié)果: 1 9 7 2 3 4 5 6 8 */
分析這段代碼的事件循環(huán)的詳細(xì)過程之前,有幾點(diǎn)需要說一下:
- 在一次事件循環(huán)中,只會執(zhí)行
一個宏任務(wù)
和所有的微任務(wù)
,而且宏任務(wù)和微任務(wù)的處理順序是固定的:每次執(zhí)行完一個宏任務(wù)后,首先會立即處理所有的微任務(wù),然后才會執(zhí)行下一個宏任務(wù)。如果在執(zhí)行微任務(wù)時又產(chǎn)生了新的微任務(wù),那么這些新的微任務(wù)也會被添加到隊(duì)列中,直到全部微任務(wù)都執(zhí)行完成,才會執(zhí)行宏任務(wù)。 - 宏任務(wù)執(zhí)行期間產(chǎn)生的微任務(wù)都會在當(dāng)前宏任務(wù)執(zhí)行完畢之后立即執(zhí)行,不會延遲到下一個宏任務(wù)或事件循環(huán)中執(zhí)行
- 當(dāng)一個宏任務(wù)執(zhí)行的過程中產(chǎn)生了微任務(wù),那么這些微任務(wù)會被推入微任務(wù)隊(duì)列中等待處理。而只有當(dāng)當(dāng)前宏任務(wù)執(zhí)行結(jié)束之后,主線程才會去處理微任務(wù)隊(duì)列中的所有微任務(wù)。因此,所有的微任務(wù)都會在下一個宏任務(wù)執(zhí)行之前被處理完畢。
- 在瀏覽器中,主線程使用
輪詢
方式來實(shí)現(xiàn)事件循環(huán)機(jī)制。在執(zhí)行完當(dāng)前的任務(wù)之后,如果宏任務(wù)隊(duì)列為空,主線程會等待一段時間,這個時間間隔是由瀏覽器廠商自行決定的,然后再次查詢宏任務(wù)隊(duì)列是否有任務(wù)需要執(zhí)行。 - setTimeout 是宏任務(wù),比如執(zhí)行
setTimeout(() => console.log('8'), 200)
,瀏覽器會創(chuàng)建一個定時器(200ms),并將回調(diào)函數(shù)和指定的時間保存在一個任務(wù)中。當(dāng)指定的時間到達(dá)時,定時器才會將這個任務(wù)推入宏任務(wù)隊(duì)列中等待處理
這段代碼大概有四次
事件循環(huán),執(zhí)行過程如下:
- 第一次事件循環(huán):
首先將 console.log('1') 加入執(zhí)行棧中,輸出 1,然后將其從執(zhí)行棧中彈出。 第一個 setTimeout 函數(shù)被調(diào)用時,瀏覽器會創(chuàng)建一個定時器(100ms),并將回調(diào)函數(shù)和指定的時間保存在一個任務(wù)中。當(dāng)指定的時間到達(dá)時,定時器會將這個任務(wù)推入宏任務(wù)隊(duì)列中等待處理 第二個 setTimeout 與第一 setTimeout 類似,等待 150ms 后會被放入宏任務(wù)隊(duì)列中 Promise.resolve().then(() => console.log('7')) 放入微任務(wù)隊(duì)列 第三個 setTimeout 與第一 setTimeout 類似,等待 200ms 后會被放入宏任務(wù)隊(duì)列中 執(zhí)行 console.log('9') 取出微任務(wù)隊(duì)列中的所有任務(wù),輸出 7
- 第二次事件循環(huán):
執(zhí)行棧為空,主線程輪詢查看宏任務(wù)隊(duì)列(微任務(wù)隊(duì)列剛才已經(jīng)清空了),此時宏任務(wù)隊(duì)列為空 100ms后,第一個setTimeout 宏任務(wù)推入宏任務(wù)隊(duì)列中,取出這個宏任務(wù)放入執(zhí)行棧中 輸出 2 執(zhí)行 `Promise.resolve().then(() => console.log('3'));`、`Promise.resolve().then(() => console.log('4'));`,放入微任務(wù)隊(duì)列 這個宏任務(wù)執(zhí)行完畢之后,主線程會轉(zhuǎn)而執(zhí)行當(dāng)前微任務(wù)隊(duì)列中的所有任務(wù),輸出 3 和 4
- 第三次事件循環(huán):
執(zhí)行棧為空,主線程輪詢宏任務(wù)隊(duì)列發(fā)現(xiàn)其為空 150ms后,第二個setTimeout 宏任務(wù)推入宏任務(wù)隊(duì)列中,取出這個宏任務(wù)放入執(zhí)行棧中 輸出 5 執(zhí)行 `Promise.resolve().then(() => console.log('6'));` 放入微任務(wù)隊(duì)列 這個宏任務(wù)執(zhí)行完畢之后,主線程會轉(zhuǎn)而執(zhí)行當(dāng)前微任務(wù)隊(duì)列中的所有任務(wù),輸出 6
- 第四次事件循環(huán):
執(zhí)行棧為空,主線程輪詢宏任務(wù)隊(duì)列發(fā)現(xiàn)其為空 200ms后,第三個setTimeout 宏任務(wù)推入宏任務(wù)隊(duì)列中,取出這個宏任務(wù)放入執(zhí)行棧中 輸出 8
宏任務(wù)優(yōu)先級
宏任務(wù)之間其實(shí)存在優(yōu)先級
。比如 click > requestAnimationFrame > setTimeout
。
- 用戶交互相關(guān)的任務(wù)具有最高的優(yōu)先級。在用戶交互(例如點(diǎn)擊)后,會將與該事件相關(guān)的任務(wù)添加到宏任務(wù)隊(duì)列中并標(biāo)記為
緊急
,從而使它們具有比其他任務(wù)更高的優(yōu)先級。這確保了與用戶直接交互相關(guān)的操作具有更快的響應(yīng)時間。 - requestAnimationFrame函數(shù),這個函數(shù)也有較高的優(yōu)先級,因?yàn)樗枰谙乱淮纹聊凰⑿轮斑M(jìn)行處理以提供平滑的動畫效果
- setTimeout 或 setInterval 添加的回調(diào)函數(shù)。通常情況下,先添加到隊(duì)列中的回調(diào)函數(shù)會優(yōu)先得到處理。它們只能保證
至少
在指定的時間后才開始執(zhí)行
請看示例:
function log(message) { const now = new Date(); console.log(`[${now.getSeconds()}:${now.getMilliseconds()}] ${message}`); } setTimeout(() => { log('setTimeout callback'); }, 0); requestAnimationFrame(() => { log('requestAnimationFrame callback'); }); document.addEventListener('click', () => { log('click event'); }); // 手動觸發(fā) click 事件 const event = new Event('click'); document.dispatchEvent(event); /* [46:280] click event [46:299] setTimeout callback [5:646] requestAnimationFrame callback */
無論測試多少次,click 總是最先輸出。但是 requestAnimationFrame 就不一定先 setTimeout 輸出,因?yàn)?requestAnimationFrame 有自己的節(jié)奏,只要不影響平滑的動畫效果,即使在 setTimeout 后面也可能。
核心特性
Node.js 核心的特性是事件驅(qū)動
(Event-driven)和非阻塞 I/O
(Non-blocking I/O):
事件驅(qū)動
- nodejs 中的異步操作基于事件,也就是說,當(dāng)某個操作完成時,Node.js 會發(fā)出一個事件來通知你,然后你就可以通過注冊事件的方式來執(zhí)行回調(diào)函數(shù)。非阻塞 I/O
- nodejs 執(zhí)行一個 I/O 操作時,它不會像傳統(tǒng)的同步阻塞 I/O 一樣等待操作完成,而是會在操作的同時繼續(xù)處理其他請求。這種方式可以避免 I/O 導(dǎo)致的阻塞,提高系統(tǒng)的吞吐量和響應(yīng)能力。
Tip:兩個特性有關(guān)系,但不是一個概念。比如可以說:基于事件驅(qū)動的非阻塞 I/O
Node.js 中的事件驅(qū)動和非阻塞 I/O 是基于事件循環(huán)實(shí)現(xiàn)的。
在 node 中,事件循環(huán)是一個持續(xù)不斷的循環(huán)過程,不斷地從事件隊(duì)列
中取出事件并處理,直到事件隊(duì)列為空。具體來說,當(dāng) Node.js 遇到一個需要異步處理的 I/O 操作時,它不會等待操作完成后再執(zhí)行下一步操作,而是將該操作放到事件隊(duì)列中,并繼續(xù)執(zhí)行下一步。當(dāng)操作完成后,Node.js 會將相應(yīng)的回調(diào)函數(shù)也放到事件隊(duì)列中,等待事件循環(huán)來處理。這樣一來,Node.js 就可以同時處理多個請求,而且不會因?yàn)槟骋粋€操作的阻塞而影響整個應(yīng)用程序的性能。
除了 I/O 操作之外,事件循環(huán)還可以用于處理定時器
、HTTP 請求
、數(shù)據(jù)庫訪問
等各種類型的事件
Tip: 事件隊(duì)列不僅包含宏任務(wù)隊(duì)列
和微任務(wù)隊(duì)列
,還有維護(hù)著幾個其他的隊(duì)列,這些隊(duì)列通過事件循環(huán)機(jī)制來實(shí)現(xiàn)異步非阻塞。其他隊(duì)列有:
- check 隊(duì)列。check 隊(duì)列用于存放 setImmediate() 的回調(diào)函數(shù)
- I/O 觀察器隊(duì)列(watcher queue)
- 關(guān)閉事件隊(duì)列(close queue)
高并發(fā)和高性能
在 Node.js 中,高并發(fā)
指的是系統(tǒng)能夠處理高并發(fā)請求的能力。不會因?yàn)橐粋€請求的處理而阻塞其他請求的執(zhí)行,系統(tǒng)能夠同時處理眾多請求。高性能
通常指的是它在處理大量并發(fā)請求時表現(xiàn)出的優(yōu)異性能。
事件循環(huán)是 Node.js 實(shí)現(xiàn)高并發(fā)和高性能的核心機(jī)制
之一。通過將計(jì)算密集型任務(wù)和 I/O 任務(wù)分離并采用異步執(zhí)行,Node.js 能夠充分利用 CPU 和內(nèi)存資源,從而實(shí)現(xiàn)高性能和高并發(fā)。
沒有事件循環(huán)
,Node.js 就無法實(shí)現(xiàn)異步 I/O 和非阻塞式編程模型。在傳統(tǒng)的阻塞式 I/O 模型中,一個 I/O 操作會一直等待數(shù)據(jù)返回,導(dǎo)致應(yīng)用程序被阻塞,無法進(jìn)行其他操作。而通過事件循環(huán)機(jī)制,Node.js 實(shí)現(xiàn)了異步 I/O,當(dāng)一個 I/O 操作被觸發(fā)后,Node.js 將其放入事件循環(huán)隊(duì)列中,然后立即執(zhí)行下一個任務(wù),不必等待當(dāng)前的 I/O 操作結(jié)束。當(dāng) I/O 操作完成時,Node.js 會將相應(yīng)的回調(diào)函數(shù)添加到事件隊(duì)列中等待執(zhí)行。
node 中的事件循環(huán)vs 瀏覽器中的事件循環(huán)
相同點(diǎn):單個主線程、單個執(zhí)行棧、有宏任務(wù)隊(duì)列和微任務(wù)隊(duì)列
不同點(diǎn):
實(shí)現(xiàn)
不同。Node.js 是一款服務(wù)端運(yùn)行時,而瀏覽器則用于頁面和交互等,場景不同,所以實(shí)現(xiàn)方式不同。Node.js 中的事件循環(huán)機(jī)制是通過 libuv 庫來實(shí)現(xiàn),因?yàn)樗哂锌缙脚_性、高效性、多功能性(除了事件循環(huán)機(jī)制外,libuv 還提供了很多其他的系統(tǒng)功能和服務(wù),能夠滿足 Node.js 在服務(wù)器端編程上的需要)等。一次事件循環(huán)
不同。瀏覽器中的一次事件循環(huán)包括一個宏任務(wù)和相關(guān)所有微任務(wù)。在 node 中,一次事件循環(huán)包含6個階段(下文會詳細(xì)介紹)
雖然兩者有不同,但它們有相同的設(shè)計(jì)目標(biāo)
:高效而可靠的方式處理異步任務(wù)
(或者說:解決 JavaScript 異步編程問題)。
原理
一次事件循環(huán)包含以下 6 個階段:
+--------------------------+ | | | timers | 計(jì)時器階段:處理 setTimeout() 和 setInterval() 定時器的回調(diào)函數(shù)。 | | +--------------------------+ | | | pending callbacks | 待定回調(diào)階段:用于處理系統(tǒng)級別的錯誤信息,例如 TCP 錯誤或者 DNS 解析異常。 | | +--------------------------+ | | | idle, prepare | 僅在內(nèi)部使用,可以忽略不計(jì)。 | | +--------------------------+ | | | poll | 輪詢階段:等待 I/O 事件(如網(wǎng)絡(luò)請求或文件 I/O 等)的發(fā)生,然后執(zhí)行對應(yīng)的回調(diào)函數(shù),并且會處理定時器相關(guān)的回調(diào)函數(shù)。 | | 如果沒有任何 I/O 事件發(fā)生,此階段可能會使事件循環(huán)阻塞。 +--------------------------+ | | | check | 檢查階段:處理 setImmediate() 的回調(diào)函數(shù)。check 的回調(diào)優(yōu)先級比 setTimeout 高,比微任務(wù)要低 | | +--------------------------+ | | | close callbacks | 關(guān)閉回調(diào)階段:處理一些關(guān)閉的回調(diào)函數(shù),比如 socket.on('close')。 | | +--------------------------+
這 6 個階段執(zhí)行順序:
- 事件循環(huán)首先會進(jìn)入
timers
階段,執(zhí)行所有超時時間到達(dá)的定時器相關(guān)的回調(diào)函數(shù)。 - 當(dāng) Node.js 執(zhí)行完 timers 階段后,就會進(jìn)入到
pending callbacks
階段。在這個階段, Node.js 會執(zhí)行一些系統(tǒng)級別的回調(diào)函數(shù),這些回調(diào)函數(shù)一般都是由 Node.js 的內(nèi)部模塊觸發(fā)的,而不是由 JavaScript 代碼直接觸發(fā)的。 - 然后進(jìn)入
poll
階段,等待 I/O 事件的發(fā)生,處理相關(guān)的回調(diào)函數(shù)。如果在此階段確定沒有任何 I/O 事件需要處理,那么事件循環(huán)會等待一定的時間,以防止 CPU 空轉(zhuǎn),這個時間會由系統(tǒng)自動設(shè)置或者手動在代碼中指定。如果有定時器在此階段需要處理,那么事件循環(huán)會回到 timers 階段繼續(xù)執(zhí)行相應(yīng)的回調(diào)函數(shù)。 - 接著進(jìn)入
check
階段,處理 setImmediate() 注冊的回調(diào)函數(shù)。setImmediate() 的優(yōu)先級比 timers 階段要高。當(dāng)事件循環(huán)進(jìn)入 check 階段時,如果發(fā)現(xiàn)事件隊(duì)列中存在 setImmediate() 的回調(diào)函數(shù),則會立即執(zhí)行該回調(diào)函數(shù)而不是繼續(xù)等待 timers 階段的到來。 - 最后進(jìn)入
close callbacks
階段,處理一些關(guān)閉的回調(diào)函數(shù)。
事件循環(huán)的每個階段都有對應(yīng)的宏任務(wù)隊(duì)列
和微任務(wù)隊(duì)列
。當(dāng)一個階段中的所有宏任務(wù)都執(zhí)行完之后,事件循環(huán)會進(jìn)入下一個階段。在該階段結(jié)束時,如果存在微任務(wù),事件循環(huán)將會在開始下一個階段之前執(zhí)行所有的微任務(wù)。這樣一來,無論在何時添加微任務(wù),都能確保先執(zhí)行所有的微任務(wù),避免了某些任務(wù)的并發(fā)問題。如果我們在某個階段中添加了多個微任務(wù),那么它們會在該階段結(jié)束時依次執(zhí)行,直到所有微任務(wù)都被處理完成,才會進(jìn)入下一個階段的宏任務(wù)隊(duì)列。
一次事件循環(huán)周期
以清空6個階段的宏任務(wù)隊(duì)列和微任務(wù)隊(duì)列來結(jié)束。
一次事件循環(huán)周期內(nèi),每個階段是否可以執(zhí)行多次
。例如此時在 poll 階段,這時 timers 階段任務(wù)隊(duì)列中有了回調(diào)函數(shù),由于 timers 的優(yōu)先級高于 poll,所以又回到 timers 階段,執(zhí)行完該階段的宏任務(wù)和微任務(wù)后,在回到 poll 階段。
總之,這 6 個階段構(gòu)成了 Node.js 的事件循環(huán)機(jī)制,確保了所有被注冊的回調(diào)函數(shù)都能得到及時、準(zhǔn)確的執(zhí)行
Tip:當(dāng)調(diào)用 setTimeout 方法時,如果超時時間還沒到,則生成的定時器宏任務(wù)也不會立刻
放入宏任務(wù)隊(duì)列中,而是會被放入計(jì)時器隊(duì)列中。計(jì)時器隊(duì)列和延遲隊(duì)列類似,都是由定時器宏任務(wù)組成的小根堆結(jié)構(gòu),每個定時器宏任務(wù)也對應(yīng)著其到期時間以及對應(yīng)的回調(diào)函數(shù)。當(dāng)超時時間到達(dá)后,Node.js 會將該定時器宏任務(wù)從計(jì)時器隊(duì)列中取出并放入宏任務(wù)隊(duì)列中,等待事件循環(huán)去執(zhí)行。
盡管事件循環(huán)的機(jī)制比較明確,但由于各種因素的影響,具體的執(zhí)行順序仍然難以精確預(yù)測
。其順序取決于當(dāng)前事件隊(duì)列中各個回調(diào)函數(shù)的執(zhí)行情況、耗時以及系統(tǒng)各種資源的利用情況等多種因素。每次事件循環(huán)的順序都不一定相同:
- 例如,在事件循環(huán)的 poll 階段中,如果存在大量耗時較長的 I/O 回調(diào)函數(shù),則事件循環(huán)可能會在 poll 階段中花費(fèi)較長的時間。此時,即使定時器的超時時間到達(dá)了,事件循環(huán)也不會立即進(jìn)入 timers 階段,而是要先處理 poll 階段中還未完成的任務(wù)。
Tip: setTimeout 在node 中最小是1ms
,在瀏覽器中是4ms
。
示例
console.log("start"); setTimeout(() => { console.log("first timeout callback"); }, 1); setImmediate(() => { console.log("immediate callback"); }); process.nextTick(() => { console.log("next tick callback"); }); console.log("end");
運(yùn)行10次node 輸出如下:
start end next tick callback first timeout callback immediate callback
執(zhí)行分析:
- 先執(zhí)行同步代碼,輸出
start
、end
- setTimeout和setImmediate屬于宏任務(wù)
- process.nextTick 是微任務(wù),輸出
next tick callback
現(xiàn)在的難點(diǎn)是 setImmediate 和 setTimeout 的回調(diào)哪個先執(zhí)行!
注:在某些特殊情況下,timers 階段和 check 階段的任務(wù)可能會交錯執(zhí)行。這通常發(fā)生在以下兩種情況下:
- 當(dāng) timers 階段中存在長時間運(yùn)行的回調(diào)函數(shù)時(如一個耗時很長的 for 循環(huán)),會導(dǎo)致該階段阻塞,影響事件循環(huán)的正常執(zhí)行。在這種情況下,如果 check 階段中有一些較短的回調(diào)函數(shù)需要執(zhí)行,Node.js 可能會在 timers 階段中間中斷執(zhí)行,并立即進(jìn)入 check 階段處理已經(jīng)準(zhǔn)備好的回調(diào)函數(shù),然后再返回 timers 階段繼續(xù)執(zhí)行剩余的回調(diào)函數(shù)。
- 當(dāng)注冊了 setImmediate() 和 setTimeout() 回調(diào)函數(shù)并且它們被分別安排到不同的事件循環(huán)周期中執(zhí)行時,這時候 setImmediate() 的回調(diào)函數(shù)可能會在 timers 階段的回調(diào)函數(shù)之前被執(zhí)行。這是因?yàn)?check 階段的任務(wù)隊(duì)列優(yōu)先級比 timers 階段的任務(wù)隊(duì)列要高,所以在下一個循環(huán)周期的 check 階段中,setImmediate() 的回調(diào)函數(shù)會被優(yōu)先處理。
根據(jù)結(jié)果,我們推測
:setImmediate 和 setTimeout 都進(jìn)入了下一個循環(huán)周期,先執(zhí)行 timers 階段,在執(zhí)行 check 階段的回調(diào)。
Tip: 盡管 setImmediate 被稱為 "immediate",但它并不保證會立刻執(zhí)行。在 Node.js 的事件循環(huán)中,setImmediate() 的回調(diào)函數(shù)會被加入到 check 階段的任務(wù)隊(duì)列中,等到輪到 check 階段時才會執(zhí)行。
CPU 密集型場景
Node.js 不適合CPU 密集型場景
。比如大量數(shù)學(xué)計(jì)算,可能會阻塞 Node.js 主線程。
比如一個 1 到 10億求和
的請求:
const http = require('http'); http.createServer((req, res) => { console.log('start'); let sum = 0; for (let i = 1; i <= 1000000000; i++) { sum += i; } console.log('end'); res.writeHead(200, {'Content-Type': 'text/plain'}); res.end(sum.toString()); }).listen(3000); console.log('server running at http://localhost:3000/');
通過curl 檢測訪問 http://localhost:3000/
的時間,分別是 1.754s
、1.072s
、2.821s
Administrator@ MINGW64 /e/ (master) $ time curl http://localhost:3000/ % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 18 0 18 0 0 15 0 --:--:-- 0:00:01 --:--:-- 15500000000067109000 real 0m1.754s user 0m0.000s sys 0m0.078s Administrator@ MINGW64 /e/ (master) $ time curl http://localhost:3000/ % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 18 0 18 0 0 20 0 --:--:-- --:--:-- --:--:-- 21500000000067109000 real 0m1.072s user 0m0.015s sys 0m0.093s Administrator@ MINGW64 /e/ (master) $ time curl http://localhost:3000/ % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 18 0 18 0 0 6 0 --:--:-- 0:00:02 --:--:-- 6500000000067109000 real 0m2.821s user 0m0.031s sys 0m0.077s
接著用node 內(nèi)置的 cluster
模塊將計(jì)算工作分配到4個子進(jìn)程中,訪問速度大幅度提升。
const http = require('http'); const cluster = require('cluster'); if (cluster.isMaster) { // 計(jì)算工作分配到4個子進(jìn)程中 const numCPUs = require('os').cpus().length; const range = 1000000000; const rangePerCore = Math.ceil(range / numCPUs); let endIndex = 0; let sum = 0; for (let i = 0; i < numCPUs; i++) { const worker = cluster.fork(); worker.on('message', function({ endIndex, result }) { sum += result; if (endIndex === range) { console.log(sum); // 啟動 Web 服務(wù)器,在主進(jìn)程中處理請求 http.createServer((req, res) => { res.statusCode = 200; res.setHeader('Content-Type', 'text/plain'); res.end(`The sum is ${sum}\n`); }).listen(3000, () => { console.log(`Server running at http://localhost:3000/`); }); } }); worker.send({ startIndex: endIndex + 1, endIndex: endIndex + rangePerCore }); endIndex += rangePerCore; } } else { process.on('message', function({ startIndex, endIndex }) { let sum = 0; for (let i = startIndex; i <= endIndex; i++) { sum += i; } process.send({ endIndex, result: sum }); }); }
訪問時長分別是:0.230s
、0.216s
、0.205s
:
Administrator@ MINGW64 /e/ (master) $ time curl http://localhost:3000/ % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 30 100 30 0 0 2354 0 --:--:-- --:--:-- --:--:-- 4285The sum is 500000000098792260 real 0m0.230s user 0m0.000s sys 0m0.109s Administrator@ MINGW64 /e/ (master) $ time curl http://localhost:3000/ % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 30 100 30 0 0 2212 0 --:--:-- --:--:-- --:--:-- 3750The sum is 500000000098792260 real 0m0.216s user 0m0.000s sys 0m0.078s Administrator@ MINGW64 /e/ (master) $ time curl http://localhost:3000/ % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 30 100 30 0 0 2545 0 --:--:-- --:--:-- --:--:-- 6000The sum is 500000000098792260 real 0m0.205s user 0m0.000s sys 0m0.078s
其他
pm2 的一個局限性
假如一個請求得花費(fèi)2秒(1 到 10億之和
),使用 pm2 也不能減小請求時間。
pm2能做的是:比如一個 node 應(yīng)用單核(1個cpu內(nèi)核)可以支持一千個并發(fā)請求,現(xiàn)在并發(fā)四千個請求,由于超出能力,請求響應(yīng)會變慢?,F(xiàn)在通過 Pm2 在四核服務(wù)器中啟動4個node應(yīng)用,之前還存在負(fù)載均衡,這樣就可以支持四千個并發(fā)請求。
Tip:pm2的介紹請看這里
單線程
Node.js 是單線程的,這意味著所有事件循環(huán)(Event Loop)和 I/O 操作都在一個主線程
中運(yùn)行。所以說,Node.js 中只存在一個事件循環(huán)和一個執(zhí)行上下文棧。
不過,Node.js 的實(shí)現(xiàn)并不簡單粗暴。它通過使用非阻塞 I/O、異步編程以及事件驅(qū)動機(jī)制,讓單線程可以支持高并發(fā)處理大量的 I/O 操作。Node.js 底層采用的是 libuv 庫來實(shí)現(xiàn)異步 I/O 模型,該庫在底層會使用 libev 和 libeio 等多種事件驅(qū)動框架來實(shí)現(xiàn)對底層 I/O 系統(tǒng)調(diào)用的封裝,從而讓單線程可以同時處理多個 I/O 任務(wù),避免了線程切換的開銷,提高了應(yīng)用程序的性能。
此外,在 Node.js 版本 10.5.0 之后,Node.js 引入了 worker_threads 模塊,支持通過創(chuàng)建子線程的方式來實(shí)現(xiàn)多線程。worker_threads 模塊提供了一套 API,使得開發(fā)者可以方便地創(chuàng)建和管理多個子線程,并利用多線程來加速處理計(jì)算密集型任務(wù)等場景。
總之,Node.js 是單線程的,但同時也通過采用異步 I/O 模型、事件驅(qū)動機(jī)制和多線程等技術(shù)手段,來支持高并發(fā)、高性能的應(yīng)用程序開發(fā)。
到此這篇關(guān)于nodejs 快速入門之事件循環(huán)的文章就介紹到這了,更多相關(guān)nodejs 事件循環(huán)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- nodeJs事件循環(huán)運(yùn)行代碼解析
- 帶你了解NodeJS事件循環(huán)
- Nodejs監(jiān)控事件循環(huán)異常示例詳解
- 詳解nodejs異步I/O和事件循環(huán)
- 我的Node.js學(xué)習(xí)之路(三)--node.js作用、回調(diào)、同步和異步代碼 以及事件循環(huán)
- Node.js事件循環(huán)(Event Loop)和線程池詳解
- 深入理解Node.js 事件循環(huán)和回調(diào)函數(shù)
- 小結(jié)Node.js中非阻塞IO和事件循環(huán)
- 深入淺析Node.js 事件循環(huán)
- 實(shí)例分析JS與Node.js中的事件循環(huán)
相關(guān)文章
把Node.js程序加入服務(wù)實(shí)現(xiàn)隨機(jī)啟動
這篇文章主要介紹了把Node.js程序加入服務(wù)實(shí)現(xiàn)隨機(jī)啟動,本文使用qckwinsvc實(shí)現(xiàn)這個需求,講解了qckwinsvc的安裝和使用,需要的朋友可以參考下2015-06-06node.js學(xué)習(xí)之事件模塊Events的使用示例
Nodejs中不存在瀏覽器中冒泡,捕獲這些行為,Nodejs中實(shí)現(xiàn)了events這個模塊,Nodejs中大多數(shù)模塊都集成了這個模塊,所以events是Nodejs中最重要的一個模塊。這篇文章主要給大家介紹了關(guān)于node.js學(xué)習(xí)教程之事件模塊Events的相關(guān)資料,需要的朋友可以參考下。2017-09-09nodejs 使用nodejs-websocket模塊實(shí)現(xiàn)點(diǎn)對點(diǎn)實(shí)時通訊
這篇文章主要介紹了nodejs 使用nodejs-websocket模塊實(shí)現(xiàn)點(diǎn)對點(diǎn)實(shí)時通訊的實(shí)例代碼,代碼簡單易懂,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2018-11-11基于游標(biāo)的分頁接口實(shí)現(xiàn)代碼示例
這篇文章主要給大家介紹了關(guān)于基于游標(biāo)的分頁接口實(shí)現(xiàn)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-11-11nodejs個人博客開發(fā)第一步 準(zhǔn)備工作
這篇文章主要為大家詳細(xì)介紹了nodejs個人博客開發(fā)的準(zhǔn)備工作,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-04-04Node.js、Socket.IO和GPT-4構(gòu)建AI聊天機(jī)器人的項(xiàng)目實(shí)踐
本文主要介紹了Node.js、Socket.IO和GPT-4構(gòu)建AI聊天機(jī)器人的項(xiàng)目實(shí)踐,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05Node.js API詳解之 dgram模塊用法實(shí)例分析
這篇文章主要介紹了Node.js API詳解之 dgram模塊用法,結(jié)合實(shí)例形式分析了Node.js API中dgram模塊基本功能、函數(shù)、使用方法及操作注意事項(xiàng),需要的朋友可以參考下2020-06-06