通常情況下,被認為是單執行緒。由主執行緒去按照編碼順序一步步執行程式程式碼,一旦遇到同步程式碼阻塞,主執行緒就會被佔用,後續的程式程式碼的執行都會被卡住。沒錯Node.js
的單執行緒指的是主執行緒是"單執行緒"。
為了解決單執行緒帶來的問題,本文的主角worker_threads
出現了。worker_threads
首次在Node.js v10.5.0
作為實驗性功能出現,需要命令列帶上--experimental-worker
才能使用。直到v12.11.0
穩定版才能正式使用。
本文將會介紹worker_threads
的使用方式,以及利用worker_threads
執行斐波那契數列作為實踐例子。
閱讀並食用本文,需要先具備:
Node.js v12.11.0
及以上版本worker_threads
模組允許使用並行執行 JavaScript 的執行緒。
工作執行緒對於執行 CPU 密集型的 JavaScript 操作很有用。 它們對 I/O 密集型的工作幫助不大。 Node.js 內建的非同步 I/O 操作比工作執行緒更高效。
與 child_process
或 cluster
不同,worker_threads
可以共用記憶體。 它們通過傳輸 ArrayBuffer
範例或共用 SharedArrayBuffer
範例來實現。
由於以下特性,worker_threads
已被證明是充分利用CPU效能的最佳解決方案:
它們執行具有多個執行緒的單個程序。
每個執行緒執行一個事件迴圈。
每個執行緒執行單個 JS 引擎範例。
每個執行緒執行單個 範例。
worker_threads
通過執行主執行緒
指定的指令碼檔案
來工作。每個執行緒都在與其他執行緒隔離的情況下執行。但是,這些執行緒可以通過訊息通道來回傳遞訊息。
主執行緒
使用worker.postMessage()
函數使用訊息通道,而工作執行緒
使用parentPort.postMessage()
函數。
通過官方範例程式碼加強了解:
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads'); if (isMainThread) { module.exports = function parseJSAsync(script) { return new Promise((resolve, reject) => { const worker = new Worker(__filename, { workerData: script }); worker.on('message', resolve); worker.on('error', reject); worker.on('exit', (code) => { if (code !== 0) reject(new Error(`Worker stopped with exit code ${code}`)); }); }); }; } else { const { parse } = require('some-js-parsing-library'); const script = workerData; parentPort.postMessage(parse(script)); }
上述程式碼主執行緒
與工作執行緒
都使用同一份檔案作為執行指令碼(__filename
為當前執行檔案路徑),通過isMainThread
來區分主執行緒
與工作執行緒
執行時邏輯。當模組對外暴露方法parseJSAsync
被呼叫時候,都將會衍生子工作執行緒去執行呼叫parse
函數。
在本節使用具體例子介紹worker_threads
的使用
建立工作執行緒
指令碼檔案workerExample.js
:
const { workerData, parentPort } = require('worker_threads') parentPort.postMessage({ welcome: workerData })
建立主執行緒
指令碼檔案main.js
:
const { Worker } = require('worker_threads') const runWorker = (workerData) => { return new Promise((resolve, reject) => { // 引入 workerExample.js `工作執行緒`指令碼檔案 const worker = new Worker('./workerExample.js', { workerData }); worker.on('message', resolve); worker.on('error', reject); worker.on('exit', (code) => { if (code !== 0) reject(new Error(`stopped with ${code} exit code`)); }) }) } const main = async () => { const result = await runWorker('hello worker threads') console.log(result); } main().catch(err => console.error(err))
控制檯命令列執行:
node main.js
輸出:
{ welcome: 'hello worker threads' }
在本節中,讓我們看一下 CPU 密集型範例,生成斐波那契數列。
如果在沒有工作執行緒的情況下完成此任務,則會隨著nth
期限的增加而阻塞主執行緒。
建立工作執行緒
指令碼檔案worker.js
const {parentPort, workerData} = require("worker_threads"); parentPort.postMessage(getFibonacciNumber(workerData.num)) function getFibonacciNumber(num) { if (num === 0) { return 0; } else if (num === 1) { return 1; } else { return getFibonacciNumber(num - 1) + getFibonacciNumber(num - 2); } }
建立主執行緒
指令碼檔案main.js
:
const {Worker} = require("worker_threads"); let number = 30; const worker = new Worker("./worker.js", {workerData: {num: number}}); worker.once("message", result => { console.log(`${number}th Fibonacci Result: ${result}`); }); worker.on("error", error => { console.log(error); }); worker.on("exit", exitCode => { console.log(`It exited with code ${exitCode}`); }) console.log("Execution in main thread");
控制檯命令列執行:
node main.js
輸出:
Execution in main thread 30th Fibonacci Result: 832040 It exited with code 0
在main.js
檔案中,我們從類的範例建立一個工作執行緒,Worker
正如我們在前面的範例中看到的那樣。
為了得到結果,我們監聽 3 個事件,
message
響應工作執行緒
發出訊息。exit
在工作執行緒
停止執行的情況下觸發的事件。error
發生錯誤時觸發。我們在最後一行main.js
,
console.log("Execution in main thread");
通過控制檯的輸出可得,主執行緒
並沒有被斐波那契數列運算執行而阻塞。
因此,只要在工作執行緒
中處理 CPU 密集型任務,我們就可以繼續處理其他任務而不必擔心阻塞主執行緒。
Node.js
在處理 CPU 密集型任務時一直因其效能而受到批評。通過有效地解決這些缺點,工作執行緒的引入提高了 Node.js 的功能。
有關worker_threads
的更多資訊,請在此處存取其官方檔案。
文章結束前留下思考,後續會在評論區做補充,歡迎一起討論。
worker_threads
執行緒空閒時候會被回收嗎?worker_threads
共用記憶體如何使用?執行緒
,那麼應該有執行緒池
?更多node相關知識,請存取:!
以上就是聊聊Node.js + worker_threads如何實現多執行緒?(詳解)的詳細內容,更多請關注TW511.COM其它相關文章!