為什麼說 Node.js 不是完全的單執行緒?如何理解?以下這篇文章就來帶大家探討一下,希望對大家有幫助!
相信大家都知道 node 是一個單執行緒程序,使用了 Event Loop 可以做到多並發。可惜這是不完全正確的。
那為什麼說 Node.js 不是完全的單執行緒的程式呢?
所有我們自己寫的Javsacript,V8, event loop都跑在同一個線程裡面,也就是main thrad 。
哎嗨,這不正說明 node 是單線程的嗎?
但也許你不知道 node 有很多模組背後都是 C code。
雖然 node 沒有給使用者暴露控制 thread 的權限,但是 C 是可以使用多執行緒的。
那麼什麼時候 node 會使用多執行緒呢?
如果一個 node 方法,背後呼叫C 的同步方法,那麼都是跑在 main thread 裡面的。
如果一個 node 方法,背後呼叫C 的非同步方法,有時候不是跑在 main thread 裡面的。
Talk is cheap, show me the code.
#這裡crypto
相關模組,很多是C 寫的。下面一段程式是計算hash的函數,一般用來儲存密碼。
import { pbkdf2Sync } from "crypto"; const startTime = Date.now(); let index = 0; for (index = 0; index < 3; index++) { pbkdf2Sync("secret", "salt", 100000, 64, "sha512"); const endTime = Date.now(); console.log(`${index} time, ${endTime - startTime}`); } const endTime = Date.now(); console.log(`in the end`);
輸出的時間,
0 time, 44 1 time, 90 2 time, 134 in the end
可以看到每次大概都是花費~45ms,程式碼 main thread 上順序執行。
注意最後的輸出是誰? 注意這裡一次 hash 在我的 cpu 需要~45ms。
import { cpus } from "os"; import { pbkdf2 } from "crypto"; console.log(cpus().length); let startTime = console.time("time-main-end"); for (let index = 0; index < 4; index++) { startTime = console.time(`time-${index}`); pbkdf2("secret", `salt${index}`, 100000, 64, "sha512", (err, derivedKey) => { if (err) throw err; console.timeEnd(`time-${index}`); }); } console.timeEnd("time-main-end");
輸出的時間,
time-main-end: 0.31ms time-2: 45.646ms time-0: 46.055ms time-3: 46.846ms time-1: 47.159ms
這裡看到,main thread 早早結束,然而每次計算的時間都是45ms,要知道一個cpu 計算hash 的時間是45ms,這裡node 絕對使用了多個線程進行hash計算。
如果我在這裡把呼叫次數改成10次,那麼時間如下,可以看到隨著CPU核數的用完,時間也在增加。再一次證明node 絕對使用了多個執行緒進行hash計算。
time-main-end: 0.451ms time-1: 44.977ms time-2: 46.069ms time-3: 50.033ms time-0: 51.381ms time-5: 96.429ms // 注意这里,从第五次时间开始增加了 time-7: 101.61ms time-4: 113.535ms time-6: 121.429ms time-9: 151.035ms time-8: 152.585ms
雖然這裡證明了,node絕對啟用了多執行緒。但是有一點小小的問題?我的電腦的CPU是AMD R5-5600U,有6個核心12執行緒啊。但是為什麼時間是從第五次開始增加的呢,node沒有完全利用我的CPU啊?
原因是什麼呢?
Node 使用了預先定義的執行緒池,這個執行緒池的大小預設是4.
export UV_THREADPOOL_SIZE=6
讓我們在看一個例子,
import { request } from "https"; const options = { hostname: "www.baidu.com", port: 443, path: "/img/PC_7ac6a6d319ba4ae29b38e5e4280e9122.png", method: "GET", }; let startTime = console.time(`main`); for (let index = 0; index < 15; index++) { startTime = console.time(`time-${index}`); const req = request(options, (res) => { console.log(`statusCode: ${res.statusCode}`); console.timeEnd(`time-${index}`); res.on("data", (d) => { // process.stdout.write(d); }); }); req.on("error", (error) => { console.error(error); }); req.end(); } console.timeEnd("main");
main: 13.927ms time-2: 83.247ms time-4: 89.641ms time-3: 91.497ms time-12: 91.661ms time-5: 94.677ms ..... time-8: 134.026ms time-1: 143.906ms time-13: 140.914ms time-10: 144.088ms
這裡主程式也早早結束了,這裡我啟動http request 去下載15次圖片,他們花費的時間並沒有成倍增加,似乎不受限於執行緒池/cpu的影響。
為什麼啊? ? Node 到底有沒有在使用線程池啊?
如果Node 背後的C 的異步方法,首先會嘗試是否有內核異步支持,例如這裡網絡請是使用epoll (Linux),如果內核沒有提供異步方式,Node才會使用自己的線程池。 。
所以 http 請求雖然是異步,不過是由核心實現的,等到核心完成後,會通知C , C 會通知給 main thread 處理callback。
那麼 Node 哪些非同步方法會使用執行緒池呢?哪些不會呢?
原生Kernal Async
Thread pool
這也是大部分Node 優化的切入點。
但是這些怎麼跟最重要的 Event Loop 結合起來呢?
相信大家都對 Event loop 非常熟悉了。 Event loop 好比一個分發員,
如果是遇到普通 javascript 程式或 callback,交給 V8 處理。
如果遇到同步方法後背是 C 寫的,交給C ,跑在 main thread。
如果遇到非同步方法後背是 C 寫的,如果有核心非同步支持,從main thread 交給核心處理。
如果是非同步方法後背是 C 寫的,如果沒有核心非同步支持,從 main thread 交給 thread pool。
thread pool 和核心有結果都會把結果回傳 event loop,如果註冊的有 javascript callback,就交給V8處理。
然後如此循環,直到沒有東西可以處理。
所以 Node 不完全是單執行緒程式。
更多node相關知識,請造訪:nodejs 教學!
以上是如何理解 Node.js 不是完全的單線程的程式(淺析)的詳細內容。更多資訊請關注PHP中文網其他相關文章!