您好,登錄后才能下訂單哦!
本篇內容介紹了“Node.js的多線程能力怎么做異步計算”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
都說 Node.js 可以實現高性能的服務器,那什么是高性能呢?
所有的軟件代碼最終都是通過 CPU 來跑的,能不能把 CPU 高效利用起來是區分性能高低的標志,也就是說不能讓它空轉。
那什么時候會空轉呢?
當程序在進行網絡和磁盤的 IO 的時候,這時候 CPU 是空閑的,也就是在空轉。
多核 CPU 可以同時跑多個程序,如果只利用了其中一核,那么其他核也是在空轉。
所以,要想達到高性能,就要解決這兩個問題。
操作系統提供了線程的抽象,對應代碼不同的執行分支,都是可以同時上不同的 CPU 跑的,這是利用好多核 CPU 性能的方式。
而如果有的線程在進行 IO 了,也就是要阻塞的等待讀寫完成,這種是比較低效的方式,所以操作系統實現了 DMA 的機制,就是設備控制器,由硬件來負責從設備到內存的搬運,在搬完了告訴 CPU 一聲。這樣當有的線程在 IO 的時候就可以把線程暫停掉,等收到 DMA 運輸數據完成的通知再繼續跑。
多線程、DMA,這是利用好多核 CPU 優勢、解決 CPU 阻塞等 IO 的問題的操作系統提供的解決方案。
而各種編程語言對這種機制做了封裝,Node.js 也是,Node.js 之所以是高性能,就是因為異步 IO 的設計。
Node.js 的異步 IO 的實現在 libuv,基于操作系統提供的異步的系統調用,這種一般是硬件級別的異步,比如 DMA 搬運數據。但是其中有一些同步的系統調用,通過 libuv 封裝以后也會變成異步的,這是因為 libuv 內有個線程池,來執行這些任務,把同步的 API 變成異步的。這個線程池的大小可以通過 UV_THREADPOOL_SIZE
的環境變量設置,默認是 4。
我們在代碼里調用的異步 API,很多都是通過線程來實現的。
比如:
const fsPromises = require('fs').promises; const data = await fsPromises.readFile('./filename');
但是,這種異步 API 只解決了 IO 的問題,那如何利用多核 CPU 的優勢來做計算呢?
Node.js 在 10.5 實驗性的引入(在 12 正式引入)了 worker_thread 模塊,可以創建線程,最終用多個 CPU 跑,這是利用多核 CPU 的做計算的方式。
異步 API 可以利用多線程做 IO,而 worker_thread 可以創建線程做計算,用于不同的目的。
要聊清楚 worker_thread,還得從瀏覽器的 web worker 聊起。
瀏覽器也同樣面臨不能利用多核 CPU 做計算的問題,所以 html5 引入了 web worker,可以通過另一個線程做計算。
<!DOCTYPE html> <html> <head></head> <body> <script> (async function () { const res = await runCalcWorker(2, 3, 3, 3); console.log(res); })(); function runCalcWorker(...nums) { return new Promise((resolve, reject) => { const calcWorker = new Worker('./webWorker.js'); calcWorker.postMessage(nums) calcWorker.onmessage = function (msg) { resolve(msg.data); }; calcWorker.onerror = reject; }); } </script> </body> </html>
我們創建一個 Worker 對象,指定跑在另一個線程的 js 代碼,然后通過 postMessage 傳遞消息給它,通過 onMessage 接收消息。這個過程也是異步的,我們進一步把它封裝成了 promise。
然后在 webWorker.js 里面接收數據,做計算,之后通過 postMessage 傳回結果。
// webWorker.js onmessage = function(msg) { if (Array.isArray(msg.data)) { const res = msg.data.reduce((total, cur) => { return total += cur; }, 0); postMessage(res); } }
這樣,我們就利用了另一個 CPU 核來跑了這段計算,對寫代碼來說和普通的異步代碼沒啥區別。但這個異步實際上不是 IO 的異步,而是計算的異步。
Node.js 的 worker thread 和 web worker 類似,我甚至懷疑 worker thread 的名字就是受 web worker 影響的。
把上面那段異步計算的邏輯在 Node.js 里面實現話,是這樣的:
const runCalcWorker = require('./runCalcWorker'); (async function () { const res = await runCalcWorker(2, 3, 3, 3); console.log(res); })();
以異步的方式調用,因為異步計算和異步 IO 在使用方式上沒啥區別。
// runCalcWorker.js const { Worker } = require('worker_threads'); module.exports = function(...nums) { return new Promise(function(resolve, reject) { const calcWorker = new Worker('./nodeWorker.js'); calcWorker.postMessage(nums); calcWorker.on('message', resolve); calcWorker.on('error', reject); }); }
然后異步計算的實現是通過創建 Worker 對象,指定在另一個線程跑的 JS,然后通過 postMessage 傳遞消息,通過 message 接收消息。這個和 web worker 很類似。
// nodeWorker.js const { parentPort } = require('worker_threads'); parentPort.on('message', (data) => { const res = data.reduce((total, cur) => { return total += cur; }, 0); parentPort.postMessage(res); });
在具體執行計算的 nodeWorker.js 里面,監聽 message 消息,然后進行計算,通過 parentPost.postMessage 傳回數據。
對比下 web worker,你會發現特別的像。所以,我覺得 Node.js 的 worker thread 的 api 是參考 web worker 來設計的。
但是,其實 worker thread 也支持在創建的時候就通過 wokerData 傳遞數據:
const { Worker } = require('worker_threads'); module.exports = function(...nums) { return new Promise(function(resolve, reject) { const calcWorker = new Worker('./nodeWorker.js', { workerData: nums }); calcWorker.on('message', resolve); calcWorker.on('error', reject); }); }
然后 worker 線程里通過 workerData 來取:
const { parentPort, workerData } = require('worker_threads'); const data = workerData; const res = data.reduce((total, cur) => { return total += cur; }, 0); parentPort.postMessage(res);
因為有個傳遞消息的機制,所以要做序列化和反序列化,像函數這種無法被序列化的數據就無法傳輸了。這也是 worker thread 的特點。
從使用上來看,都可以封裝成普通的異步調用,和其他異步 API 用起來沒啥區別。
都要經過數據的序列化反序列化,都支持 postMessage、onMessage 來收發消息。
除了 message,Node.js 的 worker thread 支持傳遞數據的方式更多,比如還有 workerData。
但從本質上來看,兩者都是為了實現異步計算,充分利用多核 CPU 的性能,沒啥區別。
高性能的程序也就是要充分利用 CPU 資源,不要讓它空轉,也就是 IO 的時候不要讓 CPU 等,多核 CPU 也要能同時利用起來做計算。操作系統提供了線程、DMA的機制來解決這種問題。Node.js 也做了相應的封裝,也就是 libuv 實現的異步 IO 的 api,但是計算的異步是 Node 12 才正式引入的,也就是 worker thread,api 設計參考了瀏覽器的 web worker,傳遞消息通過 postMessage、onMessage,需要做數據的序列化,所以函數是沒法傳遞的。
從使用上來看異步計算、異步 IO 使用方式一樣,但是異步 IO 只是讓 cpu 不同阻塞的等待 IO 完成,異步計算是利用了多核 CPU 同時進行并行的計算,數倍提升計算性能。
“Node.js的多線程能力怎么做異步計算”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。