首頁  >  文章  >  web前端  >  如何理解 Node.js 不是完全的單線程的程式(淺析)

如何理解 Node.js 不是完全的單線程的程式(淺析)

青灯夜游
青灯夜游轉載
2022-02-08 18:20:251880瀏覽

為什麼說 Node.js 不是完全的單執行緒?如何理解?以下這篇文章就來帶大家探討一下,希望對大家有幫助!

如何理解 Node.js 不是完全的單線程的程式(淺析)

相信大家都知道 node 是一個單執行緒程序,使用了 Event Loop 可以做到多並發。可惜這是不完全正確的。

那為什麼說 Node.js 不是完全的單執行緒的程式呢?

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.

#同步方法,跑在main thread 裡面

#這裡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。

非同步pbkdf2 方法,不跑在main thread 裡面

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

讓我們在看一個例子,

HTTP request

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

    • TCP/UDP server client
    • Unix Domain Sockets (IPC)
    • pipes
    • dns.resolveXXX
    • tty input(stdin etc)
    • #Unix signals
    • Child process
  • Thread pool

    • fs.*
    • dns.lookup
    • pipe (edge case)

這也是大部分Node 優化的切入點。

但是這些怎麼跟最重要的 Event Loop 結合起來呢?

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中文網其他相關文章!

陳述:
本文轉載於:juejin.cn。如有侵權,請聯絡admin@php.cn刪除