ホームページ > 記事 > ウェブフロントエンド > Node.js のマルチスレッド機能を使用して非同期計算を行う方法について話しましょう
非同期計算を行うにはどうすればよいですか?次の記事では、ブラウザのマルチスレッド機能と Node.js を使用して非同期計算を行う方法を紹介します。
Node.js でサーバーの高性能化が実現できると言われていますが、高性能とは何でしょうか?
すべてのソフトウェア コードは最終的に CPU を通じて実行されます。CPU が効率的に利用できるかどうかはパフォーマンスの指標であり、CPU がアイドル状態であってはなりません。 [推奨される学習: 「nodejs チュートリアル 」]
いつアイドル状態になりますか?
したがって、高いパフォーマンスを達成したい場合は、これら 2 つの問題を解決する必要があります。
オペレーティング システムは、スレッドの抽象化を提供します。コードに対応するさまざまな実行ブランチを、さまざまな CPU 上で同時に実行できます。これは、マルチコア CPU のパフォーマンスを活用する方法です。
一部のスレッドが IO を実行している場合、それらはブロックされ、読み取りと書き込みの完了を待ちます。これは比較的非効率的な方法であるため、オペレーティング システムはデバイス コントローラーである DMA メカニズムを実装します。ハードウェアはデバイスからメモリへの移動を担当し、移動が完了すると CPU に通知します。このようにして、一部のスレッドが IO を実行しているときに、DMA 転送データが完了したという通知を受信した後、スレッドを一時停止して実行を継続できます。
マルチスレッドと DMA は、マルチコア CPU を利用して CPU ブロッキングなどの IO 問題を解決する、オペレーティング システムによって提供されるソリューションです。
さまざまなプログラミング言語がこの仕組みをカプセル化していますが、Node.js も同様に、Node.js が高性能である理由は、非同期 IO の設計にあります。
Node.js の非同期 IO は、オペレーティング システムによって提供される非同期システム コールに基づいて、libuv に実装されています。これは、データ転送のための DMA など、一般にハードウェア レベルの非同期です。ただし、一部の同期システム コールは、libuv によってカプセル化された後に非同期になります。これは、これらのタスクを実行し、同期 API を非同期に変えるためのスレッド プールが libuv にあるためです。このスレッド プールのサイズは、UV_THREADPOOL_SIZE
環境変数を通じて設定できます。デフォルトは 4 です。
#コード内で呼び出す非同期 API の多くは、スレッドを通じて実装されます。
例:
const fsPromises = require('fs').promises; const data = await fsPromises.readFile('./filename');
ただし、この非同期 API は IO の問題を解決するだけなので、計算にマルチコア CPU を活用するにはどうすればよいでしょうか?
Node.js は、10.5 で実験的に worker_thread モジュールを導入しました (12 で正式に導入されました)。これにより、スレッドを作成し、最終的には複数の CPU で実行できます。これは、計算にマルチコア CPU を使用する方法です。
非同期 API はマルチスレッドを使用して IO を実行でき、worker_thread はさまざまな目的の計算を行うスレッドを作成できます。
worker_thread について明確に説明するには、ブラウザーの Web ワーカーから始める必要があります。
ブラウザは計算にマルチコア CPU を使用できないという問題にも直面しているため、html5 では Web ワーカーが導入されています。別のスレッドが計算します。
<!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 のワーカー スレッドは Web ワーカーに似ており、ワーカー スレッドの名前は Web ワーカーの影響を受けているのではないかとさえ疑っています。
上記の非同期計算ロジックを Node.js に実装すると、次のようになります:
const runCalcWorker = require('./runCalcWorker'); (async function () { const res = await runCalcWorker(2, 3, 3, 3); console.log(res); })();
Call非同期計算と非同期 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 ワーカーと非常によく似ています。
// 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では、メッセージメッセージをリッスンしてから計算を実行し、parentPost.postMessageを通じてデータを返します。
Web ワーカーを比較すると、特別な類似点が見つかるでしょう。そのため、Node.jsのワーカースレッドのAPIはWebワーカーを参考に設計されていると思います。
ただし、実際には、ワーカー スレッドは、ワーカー スレッドの作成時に、workerData を介してデータを渡すこともサポートしています:
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); }); }
その後、ワーカー スレッドは、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 のマルチスレッド機能を使用して非同期計算を行う方法について話しましょうの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。