想要明白workers,首先需要明白node是怎樣構成的。當一個node進程開始,它其實是:
- 一個進程。
- 一個線程。
- 一個事件輪垂。
- 一個js引擎實例。
- 一個node.js實例。
一個進程:是指一個全局對象,這個對象能夠訪問任何地方,並且包含當前處理時的此時信息。
一個線程:單線程意味着單位時間內只有一組指令在給定的進程中執行。
一個事件輪垂:這是理解Node最重要的概念。它使Node更夠異步以及擁有無鎖定I/O。即使js是單線程的,通過提供一些系統核心的操作像是回調函數,promise函數以及異步的async/await函數這些功能。
一個JS引擎實例:這是個計算機程序,用來執行js的代碼。
一個Node.js實例:一個計算機程序用來執行node.js的代碼。
一句話,Node運行在一個單線程上,每次事件輪垂只有一個進程存在。一個代碼一次執行(不是並行執行)。這個非常重要,因為它很簡單,你不用考慮並發的問題。
這么設計的原因是因為js生出來最初是用來開發客戶端交互的(像是頁面交互,表單這些),沒有對線程這種用復雜的需求。
但是,和所有的事情一樣,這樣也有缺點:如果你有cpu敏感的代碼,例如內存中有大量的來回計算的復雜數據,那么這能鎖住其他需要進行處理計算的任務。像是,你向服務器發起一個請求,應對這個請求的接口有cpu敏感的代碼,那么它就能鎖定事件輪垂進而阻止其他請求的處理(筆者:其實就是其他請求就需要長時間排隊甚至超時)。
如果主事件輪垂必須等待一個函數執行完成然后才能執行其他命令,那么這個函數就是“鎖定中”。一個無鎖定函數會允許主事件輪垂從開始就持續地運行,並且在其執行完成時通知主事件輪垂調用回調函數。
黃金准則:不要鎖定事件輪垂,盡量關注和避免那些可能造成鎖定的任務,像是同步網路調用或者無線死循環。
明白cpu操作和i/o操作是很重要的。如上所講,Node中的代碼不能並行執行。只是i/o是並行,因為他們是異步執行的。
所以,worker線程(以下我們會使用這個node特有的概念)不能提升多少i/o敏感的任務,因為異步i/o本身就比worker高效很多。worker的主要任務是提升cpu敏感操作的性能。
已有的解決方案
此外,這里已經有一些應對cpu敏感處理的方案:多進程(例如,cluster API)來保證cpu最大被利用。
這個方法好處是允許每個進程間是獨立的,如果某個線程出了問題,不會影響到其他的。他們穩定且相同的api。然而,這意味着犧牲了內存共享,並且數據通信必須用json(有額外開銷,性能稍低)。
JavaScript和Node.js是永遠不會有多線程的。原因如下:
so,有人或許會考慮給node.js添加一個新的模塊來允許我們創建一個同步線程,以此來解決cpu敏感處理的問題。
然而,這不會實現的。如果添加一個線程,這個語言的本質就會發生變化。使用類或者函數添加一個線程作為新特性是不可能。在支持多線程的語言中(如java),“synchronized”之類的關鍵字就能幫助實現多線程。
還有,一些數據不是原子的,意味着如果你不是同步處理他們,你可能的結果是在兩個線程上都可以訪問並更改這個值得變量,最后得到一個兩個線程都對這個者進行了一些改變的無效的值。例如一個簡單的0.1+20.2的操作,這個操作擁有17為小數。
因為小數點不是100%准確的,所以如果不是同步的,有一個整數可能使用worker之后得到一個非整數的數字。
最好的解決方案是
提高cpu性能的最好的方案是使用worker線程。瀏覽器很早既有了worker這個概念了。
使億有的結構從:
一個進程
一個線程
一個事件輪垂
一個JS隱情實例
一個Node.js實例
變成:
一個進程
多個線程
每個線程一個事件輪垂
每個線程一個JS隱情實例
每個線程一個Node.js實例
worker_threads模塊能夠實現使用線程實現並行執行js。
const worker = require('worker_threads');
Worker Theads在Node.10時開始可以使用,但是一直處於實驗狀態,在12.11.0時,變成穩定版本。
這個方案的意思是,在一個進程中擁有多個Node.js的實例。在worker threads中,一個線程可以有一些節點,這個節點不必是父進程。worker結束后還被分配着一些資源不是好的實踐,這會導致內存泄漏。我們想把node.js整個的潛入其中,並且給與Node.js去創建新的現成的能力,然后在線程中創建一個新的Node.js實例。本質上是獨立運行在一個進程中的線程中。
下面這些使Worker Theads與眾不同:
ArrayBuffers在線程間傳遞內存。
SharedArrayBuffer每個線程都可訪問,在線程間分享內存。(只限二進制數據)。
Atomics已可用,允許你並行執行一些處理,更高效且允許你在js中實現條件變量。
MessagePort,用來在不同線程間進行通信。可以用來傳遞結構數據,內存域,以及不同Worker之間的MessagePort(對象)。
MessageChannel代表一個異步的,雙向通信的頻道,用來在不同的(worker)線程間通信。
WorkerData用來傳遞起始數據。任意js數據的復制版本會被傳遞到這個Worker的構造函數中。如果使用postMessage(),數據也會被復制。
接口API
- const {worker, parentPort} = require('worker_threads'),worker類表示一個獨立執行js的線程,parentPort是一個message port的實例。
- new Worker(filename)或者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來監聽和接受一次性的message信息,接收到信息后也會發布一個message個豬線程。
在只支持實驗性worker thread的node版本中你必須使用--experimental-worker命令行選項來執行代碼。
其他例子:
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該類代表一個獨立的js執行線程。
isMainThead一個布爾值,當前代碼是否運行在Worker線程中。
parentPortMessagePort對象,如果當前線程是個生成的Worker線程,則允許和父線程通信。
workerData一個可以傳遞給線程構造函數的任何js數據的的復制數據。
在實戰中,上面的任務最好使用線程池來替代。否則,開銷可能大於好處。
廣州品牌設計公司https://www.houdianzi.com PPT模板下載大全https://redbox.wode007.com
對Worker的期望是什么(希望是):
- 傳遞本地處理任務。(passing native handles around)
- 鎖死檢測。鎖死是指一種情形,一系列進程被鎖定,因為每個進程都把持了一些資源,而且每個線程又在等待其他線程所把持的資源釋放然后獲取。鎖死檢測在worker thead中比較有用。
- 更多的隔離,所以一旦一個線程收到了影響,其他的沒事。
對Worker不期望的是:
- 不要認為worker會使所有的東西都很快速,有些情況下最好使用線程池。
- 不要使用worker來進行io並行操作。
- 不要認為衍生一個線程成本很低。
