自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

圖解 Node.js 的核心 Event-loop

開發(fā) 前端
Node.js 是一個完完全全的消息驅(qū)動型模型。Node進程活著的最大意義是:有各種各樣的 Event 以及綁定在 Event 上面的Callback 和Data需要它(main thread 和 worker thread)處理。

這次我們來聊聊 Node.js 里面涉及到的一個核心概念:event-loop 。只有理解了它,才能明白 node 的進程模型,也才能明白異步調(diào)用在實現(xiàn)層面是什么樣子的,更能明白當同步代碼和異步代碼混雜在一起的時候,CPU 到底跑到我們代碼的哪一行了。文章分為兩篇:event-loop 篇和 Promise/Generator/async 篇。今天我們關(guān)注 event-loop 部分。

1、代碼思考

我寫了兩個函數(shù),函數(shù)內(nèi)部直接用 while(true){} 寫了一段死循環(huán)代碼。我們先來思考下面這段 Node.js code 執(zhí)行結(jié)果是什么?很多人說 Node.js 是單線程的。如果是這樣,那 CPU 會不會陷入到 whileLoop_1() 的 while 循環(huán)里面出不來?

'use strict';
async function sleep(intervalInMS)
{
return new Promise((resolve,reject)=>{
setTimeout(resolve,intervalInMS);
});
}
async function whileLoop_1(){
while(true){
try {
console.log('new round of whileLoop_1');
await sleep(1000); // LINE-A
} catch (error) {
// ...
}
}
}
async function whileLoop_2(){
while(true){
try {
console.log('new round of whileLoop_2');
await sleep(1000); // LINE-B
} catch (error) {
// ...
}
}
}


whileLoop_1(); // LINE-C
whileLoop_2(); // LINE-D

不賣關(guān)子了,我先把執(zhí)行結(jié)果發(fā)出來。

new round of whileLoop_1
new round of whileLoop_2
new round of whileLoop_1
new round of whileLoop_2
new round of whileLoop_1
new round of whileLoop_2
new round of whileLoop_1
new round of whileLoop_2
new round of whileLoop_1
new round of whileLoop_2
...

是的,正如你所見。這兩個 while 循環(huán)分別在交替執(zhí)行, CPU 也沒有陷入到死循環(huán)里面出不來。那么問題來了:

  • CPU 執(zhí)行到 LINE-A 的時候發(fā)生了什么使得它能成功脫身并有機會執(zhí)行 whileLoop_2 ?
  • CPU 執(zhí)行到 LINE-B 后,為什么又能回到 whileLoop_1 中繼續(xù)執(zhí)行呢?

2、event-loop

在回答上面的問題前,我們需要先來看一個至關(guān)重要的概念:event-loop 。其實我們平時說 Node.js 是單線程僅僅是指 node 執(zhí)行我們的 JS 代碼,更準確地說是 V8 執(zhí)行 JS code 是發(fā)生在單線程里面的。實際上如果你打開 node 進程,會發(fā)現(xiàn)它有不少 worker thread。這是一個典型的單進程多線程模型。這些 worker thread 被放置于線程池里面,而 V8 執(zhí)行 JS code 的線程被稱為主線程。主線程和線程池的配合關(guān)系如下圖所示。主線程負責執(zhí)行 JS  code ,線程池里面的 worker thread 負責執(zhí)行類似訪問 DB、訪問文件這樣的耗時費力的工作,它倆通過消息隊列協(xié)調(diào)工作。這和餐館工作流程類似。餐館由一個長得漂亮的小姐姐招呼客人落座并負責收集來自各個餐桌的點單。每當收到一個點好的菜單時,小姐姐會迅速地把它通過一個小窗口遞交給后廚。后廚那里有一個小看板,所有的點單都被陳列在看板上。廚師長根據(jù)訂單的時間和菜品安排不同的廚師燒菜。菜燒好后,再由小姐姐負責上菜。

圖片

圖 1:Node.js 單進程多線程模型

嗯上面這張圖還是太簡單了,用來騙新手可以,我知道滿足不了你們。我們把它放大一些。下圖中左邊是主線程,右邊是線程池和一個 worker thread,中間是消息隊列。

圖片

圖 2:Node.js 主線程和工作線程關(guān)系圖

(1)主線程

主線程只干一件事:拼命地執(zhí)行 JS code,做 non-blocking I/O 操作。這些 JS code 既包含我們自己寫的,也包含我們所依賴的 npm package 。這里提到的 non-blocking I/O 操作意味著主線程干的事情基本上都是非阻塞型的工作,例如對 2+3 求和,迭代數(shù)組等。主線程以 tick 為粒度工作。是的,你一定聽說過 process.nextTick() ,所謂 next tick 就是下一次執(zhí)行 tick 的時機。每個 tick 又包含若干個 phase ,按照 Node.js 官網(wǎng)介紹,目前為止一共有 6 個 Phase。

  • timers: 這個 phase 執(zhí)行通過 setTimeout()? 和 setInterval() 所設置的 callback 函數(shù)。
  • pending callbacks: 這個 phase 執(zhí)行一些與系統(tǒng)操作相關(guān)的 callback,比如建立 TCP 連接時收到的 ECONNREFUSED 相關(guān)的 callback 。
  • idle, prepare: 僅供Node.js內(nèi)部使用。
  • poll: 從消息隊列里面獲取新的 I/O event,執(zhí)行相應的 callback (不包括 setImmediate / close callback / 以及 timer 所設置的 callback)。
  • check: 執(zhí)行通過 setImmediate()? 所設置的callback.
  • close callbacks: 執(zhí)行一些 close callback ,比如通過這樣的代碼 socket.on('close', ...)? 所設置的 callback。

絕大部分情況下,這些 callback 是用 JS 寫的,Node 通過 Google V8 engine ,在主線程里面來執(zhí)行這些 callback 。我們把上面的 6個 phase 和 tick 的關(guān)系放置到時間軸上,或許能更形象地說明主線程所做的工作。

圖片

圖 3:Node.js 主線程時序圖

(2)消息隊列

主線程不單單是在執(zhí)行 JS code,也不僅僅只是在做 non-blocking I/O 操作。它在執(zhí)行代碼的過程中,還會產(chǎn)生各種各樣的異步請求。直觀一點的如通過 setImmediate(callback[, ...args]) / fs.readFile(path[, options], callback) 產(chǎn)生,晦澀一點的如通過 Promise / async 產(chǎn)生。這些異步請求大部分情況下有一些共性:需要耗費一定的時間去處理。讓主線程放著其它事情不管,傻傻地干等這次操作的結(jié)果可不是聰明的做法。所以它們都會被封裝成 async Request,并被交給線程池去處理。還記得我們之前舉的餐館工作流程的例子嗎?燒菜是一個費時間的事情,如果小姐姐拿到我們的訂單,自己跑到后廚去燒菜會出現(xiàn)什么后果?等她把單子上的菜都燒好再去下一桌點菜的話,對客人而言就出現(xiàn)了一個 blocking I/O 操作:進餐館沒有人接待了。消息隊列就如同后廚那里的看板。小姐姐只負責往看板上添加新的訂單,而訂單的制作交由廚師團隊來完成。

(3)工作線程

工作線程來完成具體的 I/O 請求操作。通常這個過程藉由 OS 所提供的異步機制來完成。如 Windows 里面的 IO 完成端口(IOCP)、Linux 里面的異步 IO。如圖 2 所示,當工作線程完成了一個異步請求后,會把操作結(jié)果放置到一個消息隊列里面。從圖中可以看到,主線程運行所涉及到的每個 phase 都有各自專屬的消息隊列。消息隊列里面有了消息,意味著主線程又需要干活了,干活的過程中會繼續(xù)產(chǎn)生新的異步請求,工作線程繼續(xù)不知疲倦地搬磚。完美的閉環(huán)。有一種場景圖 2 并沒有畫出來,當 Node.js 收到來自系統(tǒng)外部的事件如網(wǎng)絡請求時,工作流程是什么樣子的?到目前為止我們談及的 event 都是由 JS code 主動觸發(fā)的,如果我們說這種 event 是由頂向下觸發(fā)的話,網(wǎng)絡請求這樣的 event 是由底向上觸發(fā)的。聰明的你一定可以在腦袋里大致畫出一條線出來:這條線的起點是位于內(nèi)核的網(wǎng)卡驅(qū)動,終點是 Node.js 主線程,中間依次經(jīng)過了內(nèi)核協(xié)議棧,Node.js 的消息隊列。

3、小結(jié)

行文至此,可以看到 Node.js 是一個完完全全的消息驅(qū)動型模型。Node進程活著的最大意義是:有各種各樣的 event 以及綁定在 event 上面的 callback 和 data需要它(main thread 和 worker thread)處理。event 的 callback 中也可能會產(chǎn)生新的異步請求,進而產(chǎn)生新的 event 。正是這些源源不斷的 event 驅(qū)動著 Node 活下去。如果沒有event需要Node進程處理了,它也就沒有存在的必要了。Node.js 還是一個標準的單進行多線程模型。其中主線程用來執(zhí)行我們所寫的 JS code ,而線程池里面的 worker thread 則用來執(zhí)行各種耗時長的 I/O 操作。這些操作可能會導致 worker thread 被阻塞掉。worker thread 被阻塞沒有關(guān)系,但主線程被阻塞就不太美麗了。最后再強調(diào)一下:我們所寫的 JS code 是交由 V8 在單線程里面運行的,所以盡量不要在 JS code 里面執(zhí)行耗時長的同步操作。

責任編輯:姜華 來源: 二哥聊云原生
相關(guān)推薦

2024-07-08 08:53:52

2014-03-07 13:43:32

Node.jsNode

2022-03-13 08:48:12

inspectorNode.js開發(fā)

2022-06-29 08:37:03

事件循環(huán)JS 語言

2013-11-01 09:34:56

Node.js技術(shù)

2015-03-10 10:59:18

Node.js開發(fā)指南基礎介紹

2023-04-28 15:20:37

JavaScript事件循環(huán)

2020-05-29 15:33:28

Node.js框架JavaScript

2021-12-25 22:29:57

Node.js 微任務處理事件循環(huán)

2012-02-03 09:25:39

Node.js

2017-10-09 18:54:20

前端Node.js貢獻代碼

2011-09-09 14:23:13

Node.js

2011-11-01 10:30:36

Node.js

2011-09-08 13:46:14

node.js

2011-09-02 14:47:48

Node

2012-10-24 14:56:30

IBMdw

2011-11-10 08:55:00

Node.js

2022-02-12 20:33:29

Node.jsStreamfetch API

2013-10-24 15:23:40

Event Loop

2011-11-02 09:04:15

Node.js
點贊
收藏

51CTO技術(shù)棧公眾號