原文java
對於想了解,進程,線程,io這些東西的朋友推薦個文章node
想要明白workers,首先須要明白node是怎樣構成的。
當一個node進程開始,它實際上是:chrome
一個進程:是指一個全局對象,這個對象可以訪問任何地方,而且包含當前處理時的此時信息。json
一個線程:單線程意味着單位時間內只有一組指令在給定的進程中執行。api
一個事件輪垂:這是理解Node最重要的概念。它使Node更夠異步以及擁有無鎖定I/O。即便js是單線程的,經過提供一些系統核心的操做像是回調函數,promise函數以及異步的async/await函數這些功能。promise
一個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(有額外開銷,性能稍低)。
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()
,數據也會被複制。
const {worker, parentPort} = require('worker_threads')
,worker
類表示一個獨立執行js的線程,parentPort
是一個message port的實例。 new Worker(filename)
或者new worker(code,{eval:true})
兩種開始一個worker的方法。(傳遞一個文件名字或須要執行的代碼)。建議在生產中使用文件名字。,
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線程中。parentPort
MessagePort對象,若是當前線程是個生成的Worker線程,則容許和父線程通訊。workerData
一個能夠傳遞給線程構造函數的任何js數據的的複製數據。
在實戰中,上面的任務最好使用線程池來替代。不然,開銷可能大於好處。
Workers有chrome開發工具,可用來監視Node.js中的workers。