Node 單線程到底是怎麼回事?Node多線程又是怎麼回事?但願這篇文章可以講清楚。javascript
閱讀時間大約10~13minhtml
本文測試使用環境:
系統:macOS Mojave 10.14.2
CPU:4 核 2.3 GHz
Node: 10.15.1java
通常人理解 Node 是單線程的,因此 Node 啓動後線程數應該爲 1,咱們作實驗看一下。node
setInterval(() => {
console.log(new Date().getTime())
}, 3000)
複製代碼
能夠看到 Node 進程佔用了 7 個線程。爲何會有 7 個線程呢?git
咱們都知道,Node 中最核心的是 v8 引擎,在 Node 啓動後,會建立 v8 的實例,這個實例是多線程的。github
因此你們常說的 Node 是單線程的指的是 JavaScript 的執行是單線程的,但 Javascript 的宿主環境,不管是 Node 仍是瀏覽器都是多線程的。web
Node 有兩個編譯器:
full-codegen:簡單快速地將 js 編譯成簡單可是很慢的機械碼。
Crankshaft:比較複雜的實時優化編譯器,編譯高性能的可執行代碼。api
仍是上面那個例子,咱們在定時器執行的同時,去讀一個文件:瀏覽器
const fs = require('fs')
setInterval(() => {
console.log(new Date().getTime())
}, 3000)
fs.readFile('./index.html', () => {})
複製代碼
線程數量變成了 11 個,這是由於在 Node 中有一些 IO 操做(DNS,FS)和一些 CPU 密集計算(Zlib,Crypto)會啓用 Node 的線程池,而線程池默認大小爲 4,由於線程數變成了 11。服務器
咱們能夠手動更改線程池默認大小:
process.env.UV_THREADPOOL_SIZE = 64
複製代碼
一行代碼輕鬆把線程變成 71。
Node 的單線程也帶來了一些問題,好比對 cpu 利用不足,某個未捕獲的異常可能會致使整個程序的退出等等。由於 Node 中提供了 cluster 模塊,cluster 實現了對 child_process 的封裝,經過 fork 方法建立子進程的方式實現了多進程模型。好比咱們最經常使用到的 pm2 就是其中最優秀的表明。
咱們看一個 cluster 的 demo:
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
console.log(`主進程 ${process.pid} 正在運行`);
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`工做進程 ${worker.process.pid} 已退出`);
});
} else {
// 工做進程能夠共享任何 TCP 鏈接。
// 在本例子中,共享的是 HTTP 服務器。
http.createServer((req, res) => {
res.writeHead(200);
res.end('Hello World');
}).listen(8000);
console.log(`工做進程 ${process.pid} 已啓動`);
}
複製代碼
這個時候看下活動監視器:
一共有 9 個進程,其中一個主進程,cpu 個數 x cpu 核數 = 2 x 4 = 8 個 子進程。
因此不管 child_process 仍是 cluster,都不是多線程模型,而是多進程模型。雖然開發者意識到了單線程模型的問題,可是沒有從根本上解決問題,並且提供了一個多進程的方式來模擬多線程。從前面的實驗能夠看出,雖然 Node (V8)自己是具備多線程的能力的,可是開發者並不能很好的利用這個能力,更多的是由 Node 底層提供的一些方式來使用多線程。Node 官方說:
You can use the built-in Node Worker Pool by developing a C++ addon. On older versions of Node, build your C++ addon using NAN, and on newer versions use N-API. node-webworker-threads offers a JavaScript-only way to access Node’s Worker Pool.
可是對於 JavaScript 開發者,一直沒有一個標準的、好用的方式來使用 Node 的多線程能力。
直到 Node 10.5.0 的發佈,官方纔給出了一個實驗性質的模塊 worker_threads 給 Node 提供真正的多線程能力。
先看下簡單的 demo:
const {
isMainThread,
parentPort,
workerData,
threadId,
MessageChannel,
MessagePort,
Worker
} = require('worker_threads');
function mainThread() {
for (let i = 0; i < 5; i++) {
const worker = new Worker(__filename, { workerData: i });
worker.on('exit', code => { console.log(`main: worker stopped with exit code ${code}`); });
worker.on('message', msg => {
console.log(`main: receive ${msg}`);
worker.postMessage(msg + 1);
});
}
}
function workerThread() {
console.log(`worker: workerDate ${workerData}`);
parentPort.on('message', msg => {
console.log(`worker: receive ${msg}`);
}),
parentPort.postMessage(workerData);
}
if (isMainThread) {
mainThread();
} else {
workerThread();
}
複製代碼
上述代碼在主線程中開啓五個子線程,而且主線程向子線程發送簡單的消息。
因爲 worker_thread 目前仍然處於實驗階段,因此啓動時須要增長 --experimental-worker
flag,運行後觀察活動監視器:
很少很多,正好多了五個子線程。
worker_thread 核心代碼
worker_thread 模塊中有 4 個對象和 2 個類。
threadId === 0
進行判斷的。來看一個進程通訊的例子:
const assert = require('assert');
const {
Worker,
MessageChannel,
MessagePort,
isMainThread,
parentPort
} = require('worker_threads');
if (isMainThread) {
const worker = new Worker(__filename);
const subChannel = new MessageChannel();
worker.postMessage({ hereIsYourPort: subChannel.port1 }, [subChannel.port1]);
subChannel.port2.on('message', (value) => {
console.log('received:', value);
});
} else {
parentPort.once('message', (value) => {
assert(value.hereIsYourPort instanceof MessagePort);
value.hereIsYourPort.postMessage('the worker is sending this');
value.hereIsYourPort.close();
});
}
複製代碼
更多詳細用法能夠查看官方文檔。
根據大學課本上的說法:「進程是資源分配的最小單位,線程是CPU調度的最小單位」,這句話應付考試就夠了,可是在實際工做中,咱們仍是要根據需求合理選擇。
下面對比一下多線程與多進程:
屬性 | 多進程 | 多線程 | 比較 |
---|---|---|---|
數據 | 數據共享複雜,須要用IPC;數據是分開的,同步簡單 | 由於共享進程數據,數據共享簡單,同步複雜 | 各有千秋 |
CPU、內存 | 佔用內存多,切換複雜,CPU利用率低 | 佔用內存少,切換簡單,CPU利用率高 | 多線程更好 |
銷燬、切換 | 建立銷燬、切換複雜,速度慢 | 建立銷燬、切換簡單,速度很快 | 多線程更好 |
coding | 編碼簡單、調試方便 | 編碼、調試複雜 | 多進程更好 |
可靠性 | 進程獨立運行,不會相互影響 | 線程同呼吸共命運 | 多進程更好 |
分佈式 | 可用於多機多核分佈式,易於擴展 | 只能用於多核分佈式 | 多進程更好 |
上述比較僅表示通常狀況,並不絕對。
work_thread 讓 Node 有了真正的多線程能力,算是不小的進步。
前往【IVWEB社區】公衆號查看更多幹貨文章