Comment faire un calcul asynchrone ? L'article suivant vous présentera comment utiliser les capacités multi-thread du navigateur et de Node.js pour effectuer des calculs asynchrones. J'espère qu'il vous sera utile !
On dit que Node.js peut réaliser des serveurs performants, mais qu'est-ce que la haute performance ?
Tous les codes logiciels sont finalement exécutés via le processeur. Le fait que le processeur puisse être utilisé efficacement est un signe de performances, ce qui signifie qu'il ne peut pas rester inactif. [Apprentissage recommandé : "Tutoriel Nodejs"]
Alors, quand va-t-il tourner au ralenti ?
Donc, si vous voulez atteindre des performances élevées, vous devez résoudre ces deux problèmes.
Le système d'exploitation fournit une abstraction des threads. Différentes branches d'exécution correspondant au code peuvent être exécutées sur différents CPU en même temps. C'est une manière de profiter des performances des CPU multicœurs.
Si certains threads effectuent des E/S, ils doivent bloquer et attendre la fin de la lecture et de l'écriture. Il s'agit d'une manière relativement inefficace, donc le système d'exploitation implémente le mécanisme DMA, qui est le contrôleur de périphérique, et le matériel en est responsable. pour cela. Lorsque vous déplacez l'appareil vers la mémoire, informez le processeur lorsque le déplacement est terminé. De cette façon, lorsque certains threads effectuent des E/S, ils peuvent être mis en pause et continuer à s'exécuter après avoir reçu la notification indiquant que les données de transport DMA sont terminées.
Le multi-threading et le DMA sont des solutions fournies par les systèmes d'exploitation qui tirent parti des processeurs multicœurs pour résoudre les problèmes d'E/S tels que le blocage du processeur.
Divers langages de programmation ont encapsulé ce mécanisme, et Node.js fait de même. La raison pour laquelle Node.js est haute performance est due à la conception des E/S asynchrones.
Les E/S asynchrones de Node.js sont implémentées dans libuv, sur la base d'appels système asynchrones fournis par le système d'exploitation. Il s'agit généralement d'appels système asynchrones au niveau matériel, comme le DMA pour transférer des données. Cependant, certains des appels système synchrones deviendront asynchrones après avoir été encapsulés par libuv. En effet, il existe un pool de threads dans libuv pour effectuer ces tâches et transformer l'API synchrone en asynchrone. La taille de ce pool de threads peut être définie via la variable d'environnement UV_THREADPOOL_SIZE
, et la valeur par défaut est 4.
La plupart des API asynchrones que nous appelons dans notre code sont implémentées via des threads.
Par exemple :
const fsPromises = require('fs').promises; const data = await fsPromises.readFile('./filename');
Cependant, cette API asynchrone ne résout que le problème des IO, alors comment profiter du CPU multicœur pour les calculs ?
Node.js a été introduit expérimentalement dans la version 10.5 (officiellement introduit dans la version 12) avec le module worker_thread, qui peut créer des threads et finalement les exécuter avec plusieurs processeurs. Il s'agit d'une façon d'utiliser des processeurs multicœurs pour les calculs.
L'API asynchrone peut utiliser plusieurs threads pour effectuer des E/S, et worker_thread peut créer des threads pour effectuer des calculs à différentes fins.
Pour parler clairement de worker_thread, il faut commencer par le web worker du navigateur.
Les navigateurs sont également confrontés au problème de ne pas pouvoir utiliser des processeurs multicœurs pour les calculs, donc html5 introduit des travailleurs Web, qui peuvent effectuer des calculs via un autre thread.
<!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>
Nous créons un objet Worker, spécifions le code js exécuté sur un autre thread, puis lui transmettons le message via postMessage et recevons le message via onMessage. Ce processus est également asynchrone et nous l’encapsulons dans une promesse.
Ensuite, recevez les données dans webWorker.js, effectuez des calculs, puis renvoyez les résultats via postMessage.
// webWorker.js onmessage = function(msg) { if (Array.isArray(msg.data)) { const res = msg.data.reduce((total, cur) => { return total += cur; }, 0); postMessage(res); } }
De cette façon, nous utilisons un autre cœur de processeur pour exécuter ce calcul. Pour écrire du code, ce n'est pas différent du code asynchrone ordinaire. Mais cet asynchrone n'est en fait pas asynchrone IO, mais asynchrone informatique.
Le thread de travail de Node.js est similaire au travailleur Web. Je soupçonne même que le nom du thread de travail est influencé par le travailleur Web.
Si la logique de calcul asynchrone ci-dessus est implémentée dans Node.js, elle ressemblera à ceci :
const runCalcWorker = require('./runCalcWorker'); (async function () { const res = await runCalcWorker(2, 3, 3, 3); console.log(res); })();
Appelez de manière asynchrone, car le calcul asynchrone et les E/S asynchrones sont utilisés de la même manière. façon Il n'y a aucune différence.
// 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); }); }
Ensuite, le calcul asynchrone est implémenté en créant un objet Worker, en spécifiant JS à exécuter dans un autre thread, puis en transmettant le message via postMessage et en recevant le message via message. Ceci est très similaire aux Web Workers.
// nodeWorker.js const { parentPort } = require('worker_threads'); parentPort.on('message', (data) => { const res = data.reduce((total, cur) => { return total += cur; }, 0); parentPort.postMessage(res); });
Dans nodeWorker.js qui effectue spécifiquement le calcul, écoutez le message, puis effectuez le calcul et renvoyez les données via parentPost.postMessage.
Comparez Web Worker, vous trouverez une similitude particulière. Par conséquent, je pense que l'API du thread de travail de Node.js est conçue en référence au travailleur Web.
Cependant, en fait, le thread de travail prend également en charge la transmission de données via WorkerData lors de sa création :
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); }); }
Ensuite, le thread de travail peut les récupérer via 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 同时进行并行的计算,数倍提升计算性能。
更多编程相关知识,请访问:编程视频!!
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!