Wie führt man eine asynchrone Berechnung durch? Im folgenden Artikel erfahren Sie, wie Sie die Multithreading-Funktionen des Browsers und von Node.js für asynchrone Berechnungen nutzen können.
Es heißt, dass Node.js Hochleistungsserver erreichen kann, aber was ist Hochleistungsserver?
Alle Softwarecodes werden letztendlich über die CPU ausgeführt. Ob die CPU effizient genutzt werden kann, ist ein Zeichen für die Leistung, was bedeutet, dass sie nicht im Leerlauf sein darf. [Empfohlenes Lernen: „nodejs-Tutorial“]
Wann wird es also im Leerlauf sein?
Wenn Sie also eine hohe Leistung erzielen möchten, müssen Sie diese beiden Probleme lösen.
Das Betriebssystem bietet eine Abstraktion von Threads, die dem Code entsprechen und gleichzeitig auf verschiedenen CPUs ausgeführt werden können. Dies ist eine Möglichkeit, die Leistung von Multi-Core-CPUs zu nutzen.
Wenn einige Threads E/A ausführen, müssen sie blockieren und auf den Abschluss des Lese- und Schreibvorgangs warten. Dies ist eine relativ ineffiziente Methode, sodass das Betriebssystem den DMA-Mechanismus implementiert, bei dem es sich um den Gerätecontroller handelt, und die Hardware dafür verantwortlich ist Wenn Sie das Gerät in den Speicher verschieben, benachrichtigen Sie die CPU, wenn die Verschiebung abgeschlossen ist. Auf diese Weise können einige Threads, wenn sie E/A ausführen, angehalten werden und weiterlaufen, nachdem sie die Benachrichtigung erhalten haben, dass die DMA-Transportdaten abgeschlossen sind.
Multi-Threading und DMA sind Lösungen von Betriebssystemen, die Multi-Core-CPUs nutzen und E/A-Probleme wie CPU-Blockierung lösen.
Verschiedene Programmiersprachen haben diesen Mechanismus gekapselt, und Node.js macht dasselbe. Der Grund, warum Node.js hochleistungsfähig ist, liegt im asynchronen IO-Design.
Die asynchrone E/A von Node.js ist in libuv implementiert und basiert auf asynchronen Systemaufrufen, die vom Betriebssystem bereitgestellt werden. Dies erfolgt im Allgemeinen asynchron auf Hardwareebene, z. B. DMA zur Datenübertragung. Einige der synchronen Systemaufrufe werden jedoch asynchron, nachdem sie von libuv gekapselt wurden. Dies liegt daran, dass es in libuv einen Thread-Pool gibt, der diese Aufgaben ausführt und die synchrone API in eine asynchrone umwandelt. Die Größe dieses Thread-Pools kann über die Umgebungsvariable UV_THREADPOOL_SIZE
festgelegt werden. Der Standardwert ist 4.
Viele der asynchronen APIs, die wir in unserem Code aufrufen, werden über Threads implementiert.
Zum Beispiel:
const fsPromises = require('fs').promises; const data = await fsPromises.readFile('./filename');
Diese asynchrone API löst jedoch nur das E/A-Problem. Wie kann man also die Multi-Core-CPU für Berechnungen nutzen?
Node.js wurde experimentell in 10.5 (offiziell eingeführt in 12) mit dem Modul worker_thread eingeführt, das Threads erstellen und diese letztendlich mit mehreren CPUs ausführen kann. Dies ist eine Möglichkeit, Multi-Core-CPUs für Berechnungen zu verwenden.
Asynchrone API kann Multi-Threads verwenden, um E/A durchzuführen, und worker_thread kann Threads erstellen, um Berechnungen für verschiedene Zwecke durchzuführen.
Um klar über worker_thread zu sprechen, müssen wir mit dem Web-Worker des Browsers beginnen.
Browser stehen auch vor dem Problem, dass sie keine Mehrkern-CPUs für Berechnungen verwenden können. Daher führt HTML5 Web-Worker ein, die Berechnungen über einen anderen Thread durchführen können.
<!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>
Wir erstellen ein Worker-Objekt, geben den JS-Code an, der in einem anderen Thread ausgeführt wird, leiten dann die Nachricht über postMessage an ihn weiter und empfangen die Nachricht über onMessage. Dieser Prozess ist ebenfalls asynchron und wir kapseln ihn weiter in ein Versprechen.
Dann empfangen Sie Daten in webWorker.js, führen Berechnungen durch und geben die Ergebnisse dann über postMessage zurück.
// webWorker.js onmessage = function(msg) { if (Array.isArray(msg.data)) { const res = msg.data.reduce((total, cur) => { return total += cur; }, 0); postMessage(res); } }
Auf diese Weise verwenden wir einen anderen CPU-Kern, um diese Berechnung auszuführen. Beim Schreiben von Code unterscheidet es sich nicht von gewöhnlichem asynchronem Code. Aber diese Asynchronität ist eigentlich keine E/A-Asynchronität, sondern eine rechnerische Asynchronität.
Der Worker-Thread von Node.js ähnelt dem Web-Worker. Ich vermute sogar, dass der Name des Worker-Threads vom Web-Worker beeinflusst wird.
Wenn die obige asynchrone Berechnungslogik in Node.js implementiert ist, sieht sie folgendermaßen aus:
const runCalcWorker = require('./runCalcWorker'); (async function () { const res = await runCalcWorker(2, 3, 3, 3); console.log(res); })();
Auf asynchrone Weise aufrufen, da asynchrone Berechnung und asynchrone E/A gleichzeitig verwendet werden So gibt es keinen Unterschied.
// 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); }); }
Dann wird die asynchrone Berechnung implementiert, indem ein Worker-Objekt erstellt, JS für die Ausführung in einem anderen Thread angegeben und dann die Nachricht über postMessage weitergeleitet und die Nachricht über message empfangen wird. Dies ist den Web-Workern sehr ähnlich.
// nodeWorker.js const { parentPort } = require('worker_threads'); parentPort.on('message', (data) => { const res = data.reduce((total, cur) => { return total += cur; }, 0); parentPort.postMessage(res); });
In nodeWorker.js, das speziell die Berechnung durchführt, hören Sie sich die Nachrichtennachricht an, führen Sie dann die Berechnung durch und geben Sie die Daten über parentPost.postMessage zurück.
Vergleichen Sie Web Worker, Sie werden eine besondere Ähnlichkeit feststellen. Daher denke ich, dass die Worker-Thread-API von Node.js in Bezug auf Web-Worker konzipiert ist.
Tatsächlich unterstützt der Worker-Thread jedoch auch die Weitergabe von Daten über workerData, wenn diese erstellt werden:
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); }); }
Dann kann der Worker-Thread sie über workerData abrufen:
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 同时进行并行的计算,数倍提升计算性能。
更多编程相关知识,请访问:编程视频!!
Das obige ist der detaillierte Inhalt vonLassen Sie uns darüber sprechen, wie Sie die Multithreading-Funktionen von Node.js nutzen können, um asynchrone Berechnungen durchzuführen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!