您好,登錄后才能下訂單哦!
這篇文章主要介紹了Node.js中Worker Threads的示例分析,具有一定借鑒價值,感興趣的朋友可以參考下,希望大家閱讀完這篇文章之后大有收獲,下面讓小編帶著大家一起了解一下。
理解 Node 的底層對于理解 Workers 是很有必要的。
當一個 Node.js 的應用啟動的同時,它會啟動如下模塊:
一個進程
一個線程
事件循環機制
JS 引擎實例
Node.js 實例
一個進程:process 對象是一個全局變量,可在 Node.js 程序中任意地方訪問,并提供當前進程的相關信息。
一個線程:單線程意味著在當前進程中同一時刻只有一個指令在執行。
事件循環:這是 Node.js 中需要重點理解的一個部分,盡管 JavaScript 是單線程的,但通過使用回調,promises, async/await 等語法,基于事件循環將對操作系統的操作異步化,使得 Node 擁有異步非阻塞 IO 的特性。
一個 JS 引擎實例:即一個可以運行 JavaScript 代碼的程序。
一個 Node.js 實例:即一個可以運行 Node.js 環境的程序。
換言之,Node 運行在單線程上,并且在事件循環中同一時刻只有一個進程的任務被執行,每次同一時刻只會執行一段代碼(多段代碼不會同時執行)。這是非常有效的,因為這樣的機制足夠簡單,讓你在使用 JavaScript 的時候無需擔心并發編程的問題。
這樣的原因在于 JavaScript 起初是用于客戶端的交互(比如 web 頁面的交互或表單的驗證),這些邏輯并不需要多線程這樣的機制來處理。
所以這也帶來了另一個缺點:如果你需要使用 CPU 密集型的任務,比如在內存中使用一個大的數據集進行復雜計算,它會阻塞掉其他進程的任務。同樣的,當你在發起一個有 CPU 密集型任務的遠程接口請求時,也同樣會阻塞掉其他需要被執行的請求。
如果一個函數阻塞了事件循環機制直到這個函數執行完才能執行下一個函數,那么它就被認為是一個阻塞型函數。一個非阻塞的函數是不會阻塞住事件循環進行下一個函數的執行的,它會使用回調通知事件循環函數任務已執行完畢。
最佳實踐:不要阻塞事件循環,要讓事件循環保持不斷運行,并且注意避免使用回阻塞線程的操作比如同步的網絡接口調用或死循環。
區分開 CPU 密集型操作與 I/O(input/output) 密集型操作是很重要的。像前面所說的,Node.js 并不會同時執行多段代碼,只有 I/O 操作才會同時去執行,因為它們是異步的。
所以 Worker Threads 對于 I/O 密集型操作是沒有太大的幫助的,因為異步的 I/O 操作比 worker 更有效率,Wokers 的主要作用是用于提升對于 CPU 密集型操作的性能。
此外,目前已經存在很多對于 CPU 密集型操作的解決方案,比如多進程(cluster API)方案,保證了充分利用多核 CPU。
這個方案的好處在于進程之間是相互獨立的,如果一個進程出現了問題,并不會影響到其他進程。此外它們還擁有穩定的 API,然而,這也意味著不能同享內存空間,而且進程間通信只能通過 JSON 格式的數據進行交互。
所以,人們可能會認為添加一個創建和同步線程的 Node.js 核心模塊就可以解決 CPU 密集型操作的需求。
然而并不是,如果添加多線程模塊,將會改變語言本身的特性。添加多線程模塊作為可用的類或者函數是不可能的。在一些支持多線程的語言比如 Java 中,使用同步特性來使得多個線程之間的同步能夠實現。
并且一些數字類型是不夠原子性的,這意味著如果你不同步操作它們,在多線程的同時執行計算的情況下,變量的值可能會不斷變動,沒有確定的值,變量的值可能經過一個線程計算后改變了幾個字節,在另一個線程計算后有改變了其他幾個字節的數據。比如,在 JavaScript 中一些簡單的計算像 0.1 + 0.2 的結果中小數部分有 17 位(小數的最高位數)。
var x = 0.1 + 0.2; // x will be 0.30000000000000004
但是浮點數的計算并不是 100% 精準的。所以如果不同步計算,小數部分的數字就會因為多個線程永遠沒有一個準確的數字。
所以解決 CPU 密集型操作的性能問題是使用 Worker Threads。瀏覽器在很久之前就已經有了 Workers 特性了。
單線程下的 Node.js:
一個進程
一個線程
一個事件循環
一個 JS 引擎實例
一個 Node.js 實例
多線程 Workers 下 Node.js 擁有:
一個進程
多個線程
每個線程都擁有獨立的事件循環
每個線程都擁有一個 JS 引擎實例
每個線程都擁有一個 Node.js 實例
就像下圖:
Worker_threads 模塊允許使用多個線程來同時執行 JavaScript 代碼。使用下面這個方式引入:
const worker = require('worker_threads');
Worker Threads 已經被添加到 Node.js 10 版本中,但是仍處于實驗階段。
使用 Worker threads 我們可以在在同一個進程內可以擁有多個 Node.js 實例,并且線程可以不需要跟隨父進程的終止的時候才被終止,它可以在任意時刻被終止。當 Worker 線程銷毀的時候分配給該 Worker 線程的資源依然沒有被釋放是一個很不好的操作,這會導致內存泄漏問題,我們也不希望這樣。我們希望這些分配資源能夠嵌入到 Node.js 中,讓 Node.js 有創建線程的能力,并且在線程中創建一個新的 Node.js 實例,本質上就像是在同一個進程中運行多個獨立的線程。
Worker Threads 有如下特性:
ArrayBuffers
可以將內存中的變量從一個線程轉到另外一個
SharedArrayBuffer
可以在多個線程中共享內存中的變量,但是限制為二進制格式的數據。
可用的原子操作
,可以讓你更有效率地同時執行某些操作并且實現競態變量
消息端口
,用于多個線程間通信。可以用于多個線程間傳輸結構化的數據,內存空間
消息通道
就像多線程間的一個異步的雙向通信通道。
WorkerData
是用于傳輸啟動數據。在多個線程間使用 postMessgae 進行傳輸的時候,數據會被克隆,并將克隆的數據傳輸到線程的 contructor 中。
API:
const { worker, parantPort } = require('worker_threads');
=>worker
函數相當于一個獨立的 JavaScript 運行環境線程,parentPort 是消息端口的一個實例
new Worker(filename)
or new Worker(code, { eval: true })
=>啟動 worker 的時候有兩種方式,可以通過傳輸文件路徑或者代碼,在生產環境中推薦使用文件路徑的方式。
worker.on('message')
,worker.postMessage(data)
=> 這是多線程間監聽事件與推送數據的方式。
parentPort.on('message')
, parentPort.postMessage(data)
=> 在線程中使用 parentPort.postMessage
方式推送的數據可以在父進程中使用 worker.on('message')
的方式接收到,在父進程中使用 worker.postMessage()
的方式推送的數據可以在線程中使用 parentPort.on('message')
的方式監聽到。
const { Worker } = require('worker_threads'); const worker = new Worker(` const { parentPort } = require('worker_threads'); parentPort.once('message', message => parentPort.postMessage({ pong: message })); `, { eval: true }); worker.on('message', message => console.log(message)); worker.postMessage('ping');
$ node --experimental-worker test.js { pong: ‘ping’ }
上面例子所做的也就是使用 new Worker 創建一個線程,線程中的代碼監聽了 parentPort
的消息,并且當接收到數據的時候只觸發一次回調,將收到的數據傳輸回父進程中。
你需要使用 --experimental-worker
啟動程序因為 Workers 還在實驗階段。
另一個例子:
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)); }
上面代碼中:
Worker
: 相當于一個獨立的 JavaScirpt 運行線程。
isMainThread
: 如果為 true 的話說明代碼不是運行在 Worker 線程中
parentPort
: 消息端口被使用來進行線程間通信
workerData
:被傳入 worker 的 contructor 的克隆數據。
在實際使用中,應該使用線程池的方式,不然不斷地創建 worker 線程的代價將會超過它帶來的好處。
傳輸原生的句柄比如 sockets,http 請求
死鎖檢測。死鎖是一種多個進程間被阻塞的情況,原因是每一個進程都持有一部分資源并等待另一個進程釋放它所持有的資源。在 Workers Threads 中死鎖檢測是非常有用的特性
更好的隔離,所以如果一個線程中受影響,它不會影響到其他線程。
不要認為 Workers 會帶來不可思議的速度提升,有時候使用線程池會是更好的選擇。
不要使用 Workers 來并行執行 I/O 操作。
不要認為創建 Worker 進程的開銷是很低的。
Chrome devTools 支持 Node.js 中的 Workers 線程特性。worker_threads
是一個實驗模塊,如果你需要在 Node.js 中運行 CPU 密集型的操作,目前不建議在生產環境中使用 worker 線程,可以使用進程池的方式來代替。
感謝你能夠認真閱讀完這篇文章,希望小編分享的“Node.js中Worker Threads的示例分析”這篇文章對大家有幫助,同時也希望大家多多支持億速云,關注億速云行業資訊頻道,更多相關知識等著你來學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。