nodejs?快速入門之事件循環(huán)
瀏覽器中的事件循環(huán)
請(qǐng)?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)中,只會(huì)執(zhí)行
一個(gè)宏任務(wù)
和所有的微任務(wù)
,而且宏任務(wù)和微任務(wù)的處理順序是固定的:每次執(zhí)行完一個(gè)宏任務(wù)后,首先會(huì)立即處理所有的微任務(wù),然后才會(huì)執(zhí)行下一個(gè)宏任務(wù)。如果在執(zhí)行微任務(wù)時(shí)又產(chǎn)生了新的微任務(wù),那么這些新的微任務(wù)也會(huì)被添加到隊(duì)列中,直到全部微任務(wù)都執(zhí)行完成,才會(huì)執(zhí)行宏任務(wù)。 - 宏任務(wù)執(zhí)行期間產(chǎn)生的微任務(wù)都會(huì)在當(dāng)前宏任務(wù)執(zhí)行完畢之后立即執(zhí)行,不會(huì)延遲到下一個(gè)宏任務(wù)或事件循環(huán)中執(zhí)行
- 當(dāng)一個(gè)宏任務(wù)執(zhí)行的過程中產(chǎn)生了微任務(wù),那么這些微任務(wù)會(huì)被推入微任務(wù)隊(duì)列中等待處理。而只有當(dāng)當(dāng)前宏任務(wù)執(zhí)行結(jié)束之后,主線程才會(huì)去處理微任務(wù)隊(duì)列中的所有微任務(wù)。因此,所有的微任務(wù)都會(huì)在下一個(gè)宏任務(wù)執(zhí)行之前被處理完畢。
- 在瀏覽器中,主線程使用
輪詢
方式來實(shí)現(xiàn)事件循環(huán)機(jī)制。在執(zhí)行完當(dāng)前的任務(wù)之后,如果宏任務(wù)隊(duì)列為空,主線程會(huì)等待一段時(shí)間,這個(gè)時(shí)間間隔是由瀏覽器廠商自行決定的,然后再次查詢宏任務(wù)隊(duì)列是否有任務(wù)需要執(zhí)行。 - setTimeout 是宏任務(wù),比如執(zhí)行
setTimeout(() => console.log('8'), 200)
,瀏覽器會(huì)創(chuàng)建一個(gè)定時(shí)器(200ms),并將回調(diào)函數(shù)和指定的時(shí)間保存在一個(gè)任務(wù)中。當(dāng)指定的時(shí)間到達(dá)時(shí),定時(shí)器才會(huì)將這個(gè)任務(wù)推入宏任務(wù)隊(duì)列中等待處理
這段代碼大概有四次
事件循環(huán),執(zhí)行過程如下:
- 第一次事件循環(huán):
首先將 console.log('1') 加入執(zhí)行棧中,輸出 1,然后將其從執(zhí)行棧中彈出。 第一個(gè) setTimeout 函數(shù)被調(diào)用時(shí),瀏覽器會(huì)創(chuàng)建一個(gè)定時(shí)器(100ms),并將回調(diào)函數(shù)和指定的時(shí)間保存在一個(gè)任務(wù)中。當(dāng)指定的時(shí)間到達(dá)時(shí),定時(shí)器會(huì)將這個(gè)任務(wù)推入宏任務(wù)隊(duì)列中等待處理 第二個(gè) setTimeout 與第一 setTimeout 類似,等待 150ms 后會(huì)被放入宏任務(wù)隊(duì)列中 Promise.resolve().then(() => console.log('7')) 放入微任務(wù)隊(duì)列 第三個(gè) setTimeout 與第一 setTimeout 類似,等待 200ms 后會(huì)被放入宏任務(wù)隊(duì)列中 執(zhí)行 console.log('9') 取出微任務(wù)隊(duì)列中的所有任務(wù),輸出 7
- 第二次事件循環(huán):
執(zhí)行棧為空,主線程輪詢查看宏任務(wù)隊(duì)列(微任務(wù)隊(duì)列剛才已經(jīng)清空了),此時(shí)宏任務(wù)隊(duì)列為空 100ms后,第一個(gè)setTimeout 宏任務(wù)推入宏任務(wù)隊(duì)列中,取出這個(gè)宏任務(wù)放入執(zhí)行棧中 輸出 2 執(zhí)行 `Promise.resolve().then(() => console.log('3'));`、`Promise.resolve().then(() => console.log('4'));`,放入微任務(wù)隊(duì)列 這個(gè)宏任務(wù)執(zhí)行完畢之后,主線程會(huì)轉(zhuǎn)而執(zhí)行當(dāng)前微任務(wù)隊(duì)列中的所有任務(wù),輸出 3 和 4
- 第三次事件循環(huán):
執(zhí)行棧為空,主線程輪詢宏任務(wù)隊(duì)列發(fā)現(xiàn)其為空 150ms后,第二個(gè)setTimeout 宏任務(wù)推入宏任務(wù)隊(duì)列中,取出這個(gè)宏任務(wù)放入執(zhí)行棧中 輸出 5 執(zhí)行 `Promise.resolve().then(() => console.log('6'));` 放入微任務(wù)隊(duì)列 這個(gè)宏任務(wù)執(zhí)行完畢之后,主線程會(huì)轉(zhuǎn)而執(zhí)行當(dāng)前微任務(wù)隊(duì)列中的所有任務(wù),輸出 6
- 第四次事件循環(huán):
執(zhí)行棧為空,主線程輪詢宏任務(wù)隊(duì)列發(fā)現(xiàn)其為空 200ms后,第三個(gè)setTimeout 宏任務(wù)推入宏任務(wù)隊(duì)列中,取出這個(gè)宏任務(wù)放入執(zhí)行棧中 輸出 8
宏任務(wù)優(yōu)先級(jí)
宏任務(wù)之間其實(shí)存在優(yōu)先級(jí)
。比如 click > requestAnimationFrame > setTimeout
。
- 用戶交互相關(guān)的任務(wù)具有最高的優(yōu)先級(jí)。在用戶交互(例如點(diǎn)擊)后,會(huì)將與該事件相關(guān)的任務(wù)添加到宏任務(wù)隊(duì)列中并標(biāo)記為
緊急
,從而使它們具有比其他任務(wù)更高的優(yōu)先級(jí)。這確保了與用戶直接交互相關(guān)的操作具有更快的響應(yīng)時(shí)間。 - requestAnimationFrame函數(shù),這個(gè)函數(shù)也有較高的優(yōu)先級(jí),因?yàn)樗枰谙乱淮纹聊凰⑿轮斑M(jìn)行處理以提供平滑的動(dòng)畫效果
- setTimeout 或 setInterval 添加的回調(diào)函數(shù)。通常情況下,先添加到隊(duì)列中的回調(diào)函數(shù)會(huì)優(yōu)先得到處理。它們只能保證
至少
在指定的時(shí)間后才開始執(zhí)行
請(qǐng)看示例:
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'); }); // 手動(dòng)觸發(fā) click 事件 const event = new Event('click'); document.dispatchEvent(event); /* [46:280] click event [46:299] setTimeout callback [5:646] requestAnimationFrame callback */
無論測(cè)試多少次,click 總是最先輸出。但是 requestAnimationFrame 就不一定先 setTimeout 輸出,因?yàn)?requestAnimationFrame 有自己的節(jié)奏,只要不影響平滑的動(dòng)畫效果,即使在 setTimeout 后面也可能。
核心特性
Node.js 核心的特性是事件驅(qū)動(dòng)
(Event-driven)和非阻塞 I/O
(Non-blocking I/O):
事件驅(qū)動(dòng)
- nodejs 中的異步操作基于事件,也就是說,當(dāng)某個(gè)操作完成時(shí),Node.js 會(huì)發(fā)出一個(gè)事件來通知你,然后你就可以通過注冊(cè)事件的方式來執(zhí)行回調(diào)函數(shù)。非阻塞 I/O
- nodejs 執(zhí)行一個(gè) I/O 操作時(shí),它不會(huì)像傳統(tǒng)的同步阻塞 I/O 一樣等待操作完成,而是會(huì)在操作的同時(shí)繼續(xù)處理其他請(qǐng)求。這種方式可以避免 I/O 導(dǎo)致的阻塞,提高系統(tǒng)的吞吐量和響應(yīng)能力。
Tip:兩個(gè)特性有關(guān)系,但不是一個(gè)概念。比如可以說:基于事件驅(qū)動(dòng)的非阻塞 I/O
Node.js 中的事件驅(qū)動(dòng)和非阻塞 I/O 是基于事件循環(huán)實(shí)現(xiàn)的。
在 node 中,事件循環(huán)是一個(gè)持續(xù)不斷的循環(huán)過程,不斷地從事件隊(duì)列
中取出事件并處理,直到事件隊(duì)列為空。具體來說,當(dāng) Node.js 遇到一個(gè)需要異步處理的 I/O 操作時(shí),它不會(huì)等待操作完成后再執(zhí)行下一步操作,而是將該操作放到事件隊(duì)列中,并繼續(xù)執(zhí)行下一步。當(dāng)操作完成后,Node.js 會(huì)將相應(yīng)的回調(diào)函數(shù)也放到事件隊(duì)列中,等待事件循環(huán)來處理。這樣一來,Node.js 就可以同時(shí)處理多個(gè)請(qǐng)求,而且不會(huì)因?yàn)槟骋粋€(gè)操作的阻塞而影響整個(gè)應(yīng)用程序的性能。
除了 I/O 操作之外,事件循環(huán)還可以用于處理定時(shí)器
、HTTP 請(qǐng)求
、數(shù)據(jù)庫(kù)訪問
等各種類型的事件
Tip: 事件隊(duì)列不僅包含宏任務(wù)隊(duì)列
和微任務(wù)隊(duì)列
,還有維護(hù)著幾個(gè)其他的隊(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ā)請(qǐng)求的能力。不會(huì)因?yàn)橐粋€(gè)請(qǐng)求的處理而阻塞其他請(qǐng)求的執(zhí)行,系統(tǒng)能夠同時(shí)處理眾多請(qǐng)求。高性能
通常指的是它在處理大量并發(fā)請(qǐng)求時(shí)表現(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 模型中,一個(gè) I/O 操作會(huì)一直等待數(shù)據(jù)返回,導(dǎo)致應(yīng)用程序被阻塞,無法進(jìn)行其他操作。而通過事件循環(huán)機(jī)制,Node.js 實(shí)現(xiàn)了異步 I/O,當(dāng)一個(gè) I/O 操作被觸發(fā)后,Node.js 將其放入事件循環(huán)隊(duì)列中,然后立即執(zhí)行下一個(gè)任務(wù),不必等待當(dāng)前的 I/O 操作結(jié)束。當(dāng) I/O 操作完成時(shí),Node.js 會(huì)將相應(yīng)的回調(diào)函數(shù)添加到事件隊(duì)列中等待執(zhí)行。
node 中的事件循環(huán)vs 瀏覽器中的事件循環(huán)
相同點(diǎn):?jiǎn)蝹€(gè)主線程、單個(gè)執(zhí)行棧、有宏任務(wù)隊(duì)列和微任務(wù)隊(duì)列
不同點(diǎn):
實(shí)現(xiàn)
不同。Node.js 是一款服務(wù)端運(yùn)行時(shí),而瀏覽器則用于頁(yè)面和交互等,場(chǎng)景不同,所以實(shí)現(xiàn)方式不同。Node.js 中的事件循環(huán)機(jī)制是通過 libuv 庫(kù)來實(shí)現(xiàn),因?yàn)樗哂锌缙脚_(tái)性、高效性、多功能性(除了事件循環(huán)機(jī)制外,libuv 還提供了很多其他的系統(tǒng)功能和服務(wù),能夠滿足 Node.js 在服務(wù)器端編程上的需要)等。一次事件循環(huán)
不同。瀏覽器中的一次事件循環(huán)包括一個(gè)宏任務(wù)和相關(guān)所有微任務(wù)。在 node 中,一次事件循環(huán)包含6個(gè)階段(下文會(huì)詳細(xì)介紹)
雖然兩者有不同,但它們有相同的設(shè)計(jì)目標(biāo)
:高效而可靠的方式處理異步任務(wù)
(或者說:解決 JavaScript 異步編程問題)。
原理
一次事件循環(huán)包含以下 6 個(gè)階段:
+--------------------------+ | | | timers | 計(jì)時(shí)器階段:處理 setTimeout() 和 setInterval() 定時(shí)器的回調(diào)函數(shù)。 | | +--------------------------+ | | | pending callbacks | 待定回調(diào)階段:用于處理系統(tǒng)級(jí)別的錯(cuò)誤信息,例如 TCP 錯(cuò)誤或者 DNS 解析異常。 | | +--------------------------+ | | | idle, prepare | 僅在內(nèi)部使用,可以忽略不計(jì)。 | | +--------------------------+ | | | poll | 輪詢階段:等待 I/O 事件(如網(wǎng)絡(luò)請(qǐng)求或文件 I/O 等)的發(fā)生,然后執(zhí)行對(duì)應(yīng)的回調(diào)函數(shù),并且會(huì)處理定時(shí)器相關(guān)的回調(diào)函數(shù)。 | | 如果沒有任何 I/O 事件發(fā)生,此階段可能會(huì)使事件循環(huán)阻塞。 +--------------------------+ | | | check | 檢查階段:處理 setImmediate() 的回調(diào)函數(shù)。check 的回調(diào)優(yōu)先級(jí)比 setTimeout 高,比微任務(wù)要低 | | +--------------------------+ | | | close callbacks | 關(guān)閉回調(diào)階段:處理一些關(guān)閉的回調(diào)函數(shù),比如 socket.on('close')。 | | +--------------------------+
這 6 個(gè)階段執(zhí)行順序:
- 事件循環(huán)首先會(huì)進(jìn)入
timers
階段,執(zhí)行所有超時(shí)時(shí)間到達(dá)的定時(shí)器相關(guān)的回調(diào)函數(shù)。 - 當(dāng) Node.js 執(zhí)行完 timers 階段后,就會(huì)進(jìn)入到
pending callbacks
階段。在這個(gè)階段, Node.js 會(huì)執(zhí)行一些系統(tǒng)級(jí)別的回調(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)會(huì)等待一定的時(shí)間,以防止 CPU 空轉(zhuǎn),這個(gè)時(shí)間會(huì)由系統(tǒng)自動(dòng)設(shè)置或者手動(dòng)在代碼中指定。如果有定時(shí)器在此階段需要處理,那么事件循環(huán)會(huì)回到 timers 階段繼續(xù)執(zhí)行相應(yīng)的回調(diào)函數(shù)。 - 接著進(jìn)入
check
階段,處理 setImmediate() 注冊(cè)的回調(diào)函數(shù)。setImmediate() 的優(yōu)先級(jí)比 timers 階段要高。當(dāng)事件循環(huán)進(jìn)入 check 階段時(shí),如果發(fā)現(xiàn)事件隊(duì)列中存在 setImmediate() 的回調(diào)函數(shù),則會(huì)立即執(zhí)行該回調(diào)函數(shù)而不是繼續(xù)等待 timers 階段的到來。 - 最后進(jìn)入
close callbacks
階段,處理一些關(guān)閉的回調(diào)函數(shù)。
事件循環(huán)的每個(gè)階段都有對(duì)應(yīng)的宏任務(wù)隊(duì)列
和微任務(wù)隊(duì)列
。當(dāng)一個(gè)階段中的所有宏任務(wù)都執(zhí)行完之后,事件循環(huán)會(huì)進(jìn)入下一個(gè)階段。在該階段結(jié)束時(shí),如果存在微任務(wù),事件循環(huán)將會(huì)在開始下一個(gè)階段之前執(zhí)行所有的微任務(wù)。這樣一來,無論在何時(shí)添加微任務(wù),都能確保先執(zhí)行所有的微任務(wù),避免了某些任務(wù)的并發(fā)問題。如果我們?cè)谀硞€(gè)階段中添加了多個(gè)微任務(wù),那么它們會(huì)在該階段結(jié)束時(shí)依次執(zhí)行,直到所有微任務(wù)都被處理完成,才會(huì)進(jìn)入下一個(gè)階段的宏任務(wù)隊(duì)列。
一次事件循環(huán)周期
以清空6個(gè)階段的宏任務(wù)隊(duì)列和微任務(wù)隊(duì)列來結(jié)束。
一次事件循環(huán)周期內(nèi),每個(gè)階段是否可以執(zhí)行多次
。例如此時(shí)在 poll 階段,這時(shí) timers 階段任務(wù)隊(duì)列中有了回調(diào)函數(shù),由于 timers 的優(yōu)先級(jí)高于 poll,所以又回到 timers 階段,執(zhí)行完該階段的宏任務(wù)和微任務(wù)后,在回到 poll 階段。
總之,這 6 個(gè)階段構(gòu)成了 Node.js 的事件循環(huán)機(jī)制,確保了所有被注冊(cè)的回調(diào)函數(shù)都能得到及時(shí)、準(zhǔn)確的執(zhí)行
Tip:當(dāng)調(diào)用 setTimeout 方法時(shí),如果超時(shí)時(shí)間還沒到,則生成的定時(shí)器宏任務(wù)也不會(huì)立刻
放入宏任務(wù)隊(duì)列中,而是會(huì)被放入計(jì)時(shí)器隊(duì)列中。計(jì)時(shí)器隊(duì)列和延遲隊(duì)列類似,都是由定時(shí)器宏任務(wù)組成的小根堆結(jié)構(gòu),每個(gè)定時(shí)器宏任務(wù)也對(duì)應(yīng)著其到期時(shí)間以及對(duì)應(yīng)的回調(diào)函數(shù)。當(dāng)超時(shí)時(shí)間到達(dá)后,Node.js 會(huì)將該定時(shí)器宏任務(wù)從計(jì)時(shí)器隊(duì)列中取出并放入宏任務(wù)隊(duì)列中,等待事件循環(huán)去執(zhí)行。
盡管事件循環(huán)的機(jī)制比較明確,但由于各種因素的影響,具體的執(zhí)行順序仍然難以精確預(yù)測(cè)
。其順序取決于當(dāng)前事件隊(duì)列中各個(gè)回調(diào)函數(shù)的執(zhí)行情況、耗時(shí)以及系統(tǒng)各種資源的利用情況等多種因素。每次事件循環(huán)的順序都不一定相同:
- 例如,在事件循環(huán)的 poll 階段中,如果存在大量耗時(shí)較長(zhǎng)的 I/O 回調(diào)函數(shù),則事件循環(huán)可能會(huì)在 poll 階段中花費(fèi)較長(zhǎng)的時(shí)間。此時(shí),即使定時(shí)器的超時(shí)時(shí)間到達(dá)了,事件循環(huán)也不會(huì)立即進(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)哪個(gè)先執(zhí)行!
注:在某些特殊情況下,timers 階段和 check 階段的任務(wù)可能會(huì)交錯(cuò)執(zhí)行。這通常發(fā)生在以下兩種情況下:
- 當(dāng) timers 階段中存在長(zhǎng)時(shí)間運(yùn)行的回調(diào)函數(shù)時(shí)(如一個(gè)耗時(shí)很長(zhǎng)的 for 循環(huán)),會(huì)導(dǎo)致該階段阻塞,影響事件循環(huán)的正常執(zhí)行。在這種情況下,如果 check 階段中有一些較短的回調(diào)函數(shù)需要執(zhí)行,Node.js 可能會(huì)在 timers 階段中間中斷執(zhí)行,并立即進(jìn)入 check 階段處理已經(jīng)準(zhǔn)備好的回調(diào)函數(shù),然后再返回 timers 階段繼續(xù)執(zhí)行剩余的回調(diào)函數(shù)。
- 當(dāng)注冊(cè)了 setImmediate() 和 setTimeout() 回調(diào)函數(shù)并且它們被分別安排到不同的事件循環(huán)周期中執(zhí)行時(shí),這時(shí)候 setImmediate() 的回調(diào)函數(shù)可能會(huì)在 timers 階段的回調(diào)函數(shù)之前被執(zhí)行。這是因?yàn)?check 階段的任務(wù)隊(duì)列優(yōu)先級(jí)比 timers 階段的任務(wù)隊(duì)列要高,所以在下一個(gè)循環(huán)周期的 check 階段中,setImmediate() 的回調(diào)函數(shù)會(huì)被優(yōu)先處理。
根據(jù)結(jié)果,我們推測(cè)
:setImmediate 和 setTimeout 都進(jìn)入了下一個(gè)循環(huán)周期,先執(zhí)行 timers 階段,在執(zhí)行 check 階段的回調(diào)。
Tip: 盡管 setImmediate 被稱為 "immediate",但它并不保證會(huì)立刻執(zhí)行。在 Node.js 的事件循環(huán)中,setImmediate() 的回調(diào)函數(shù)會(huì)被加入到 check 階段的任務(wù)隊(duì)列中,等到輪到 check 階段時(shí)才會(huì)執(zhí)行。
CPU 密集型場(chǎng)景
Node.js 不適合CPU 密集型場(chǎng)景
。比如大量數(shù)學(xué)計(jì)算,可能會(huì)阻塞 Node.js 主線程。
比如一個(gè) 1 到 10億求和
的請(qǐng)求:
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 檢測(cè)訪問 http://localhost:3000/
的時(shí)間,分別是 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個(gè)子進(jìn)程中,訪問速度大幅度提升。
const http = require('http'); const cluster = require('cluster'); if (cluster.isMaster) { // 計(jì)算工作分配到4個(gè)子進(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); // 啟動(dòng) Web 服務(wù)器,在主進(jìn)程中處理請(qǐng)求 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 }); }); }
訪問時(shí)長(zhǎng)分別是: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 的一個(gè)局限性
假如一個(gè)請(qǐng)求得花費(fèi)2秒(1 到 10億之和
),使用 pm2 也不能減小請(qǐng)求時(shí)間。
pm2能做的是:比如一個(gè) node 應(yīng)用單核(1個(gè)cpu內(nèi)核)可以支持一千個(gè)并發(fā)請(qǐng)求,現(xiàn)在并發(fā)四千個(gè)請(qǐng)求,由于超出能力,請(qǐng)求響應(yīng)會(huì)變慢?,F(xiàn)在通過 Pm2 在四核服務(wù)器中啟動(dòng)4個(gè)node應(yīng)用,之前還存在負(fù)載均衡,這樣就可以支持四千個(gè)并發(fā)請(qǐng)求。
Tip:pm2的介紹請(qǐng)看這里
單線程
Node.js 是單線程的,這意味著所有事件循環(huán)(Event Loop)和 I/O 操作都在一個(gè)主線程
中運(yùn)行。所以說,Node.js 中只存在一個(gè)事件循環(huán)和一個(gè)執(zhí)行上下文棧。
不過,Node.js 的實(shí)現(xiàn)并不簡(jiǎn)單粗暴。它通過使用非阻塞 I/O、異步編程以及事件驅(qū)動(dòng)機(jī)制,讓單線程可以支持高并發(fā)處理大量的 I/O 操作。Node.js 底層采用的是 libuv 庫(kù)來實(shí)現(xiàn)異步 I/O 模型,該庫(kù)在底層會(huì)使用 libev 和 libeio 等多種事件驅(qū)動(dòng)框架來實(shí)現(xiàn)對(duì)底層 I/O 系統(tǒng)調(diào)用的封裝,從而讓單線程可以同時(shí)處理多個(gè) 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)建和管理多個(gè)子線程,并利用多線程來加速處理計(jì)算密集型任務(wù)等場(chǎng)景。
總之,Node.js 是單線程的,但同時(shí)也通過采用異步 I/O 模型、事件驅(qū)動(dòng)機(jī)制和多線程等技術(shù)手段,來支持高并發(fā)、高性能的應(yīng)用程序開發(fā)。
到此這篇關(guān)于nodejs 快速入門之事件循環(huán)的文章就介紹到這了,更多相關(guān)nodejs 事件循環(huán)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(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ī)啟動(dòng)
這篇文章主要介紹了把Node.js程序加入服務(wù)實(shí)現(xiàn)隨機(jī)啟動(dòng),本文使用qckwinsvc實(shí)現(xiàn)這個(gè)需求,講解了qckwinsvc的安裝和使用,需要的朋友可以參考下2015-06-06node.js學(xué)習(xí)之事件模塊Events的使用示例
Nodejs中不存在瀏覽器中冒泡,捕獲這些行為,Nodejs中實(shí)現(xiàn)了events這個(gè)模塊,Nodejs中大多數(shù)模塊都集成了這個(gè)模塊,所以events是Nodejs中最重要的一個(gè)模塊。這篇文章主要給大家介紹了關(guān)于node.js學(xué)習(xí)教程之事件模塊Events的相關(guān)資料,需要的朋友可以參考下。2017-09-09nodejs 使用nodejs-websocket模塊實(shí)現(xiàn)點(diǎn)對(duì)點(diǎn)實(shí)時(shí)通訊
這篇文章主要介紹了nodejs 使用nodejs-websocket模塊實(shí)現(xiàn)點(diǎn)對(duì)點(diǎn)實(shí)時(shí)通訊的實(shí)例代碼,代碼簡(jiǎn)單易懂,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2018-11-11基于游標(biāo)的分頁(yè)接口實(shí)現(xiàn)代碼示例
這篇文章主要給大家介紹了關(guān)于基于游標(biāo)的分頁(yè)接口實(shí)現(xiàn)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-11-11nodejs個(gè)人博客開發(fā)第一步 準(zhǔn)備工作
這篇文章主要為大家詳細(xì)介紹了nodejs個(gè)人博客開發(fā)的準(zhǔn)備工作,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下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ì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(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