Die Praxis des Arbeitens mit Threads in Node.js 10.5.0

In jüngerer Zeit wurde Version 10.5.0 der Node.js-Plattform veröffentlicht. Eine der Hauptfunktionen war die Unterstützung für die Arbeit mit Streams, die zuerst zu Node.js hinzugefügt wurde, während sie noch experimentell war. Diese Tatsache ist besonders interessant angesichts der Tatsache, dass die Plattform jetzt diese Gelegenheit hat, deren Anhänger immer stolz darauf waren, dass sie aufgrund des fantastischen asynchronen E / A-Subsystems keine Streams benötigt. Thread-Unterstützung wurde jedoch in Node.js angezeigt. Warum sollte das so sein? Wem und warum können sie nützlich sein?



Kurz gesagt, dies ist notwendig, damit die Node.js-Plattform in den Bereichen, in denen sie zuvor nicht die bemerkenswertesten Ergebnisse zeigte, neue Höhen erreichen kann. Wir sprechen über die Durchführung von Berechnungen, bei denen Prozessorressourcen intensiv genutzt werden. Dies ist hauptsächlich der Grund dafür, dass Node.js in Bereichen wie künstliche Intelligenz, maschinelles Lernen und der Verarbeitung großer Datenmengen nicht sehr stark ist. Es wurden große Anstrengungen unternommen, um Node.js zu ermöglichen, sich bei der Lösung solcher Probleme gut zu zeigen, aber hier sieht diese Plattform immer noch viel bescheidener aus als beispielsweise bei der Entwicklung von Mikrodiensten.

Der Autor des Materials, dessen Übersetzung wir heute veröffentlichen, sagte, er habe beschlossen, die technische Dokumentation, die in der ursprünglichen Pull-Anfrage und in offiziellen Quellen enthalten ist , auf eine Reihe einfacher praktischer Beispiele zu reduzieren. Er hofft, dass jeder, der sich diese Beispiele ansieht, genug weiß, um mit Threads in Node.js zu beginnen.

Informationen zum Modul worker_threads und zum Flag --experimental-worker


Die Multithreading-Unterstützung in Node.js ist als Modul worker_threads . Um die neue Funktion nutzen zu können, muss dieses Modul daher mit dem Befehl require verbunden werden.

Beachten Sie, dass Sie beim Ausführen des Skripts nur mit worker_threads indem Sie das Flag - experimental-worker verwenden. Andernfalls findet das System dieses Modul nicht.

Beachten Sie, dass das Flag das Wort "Arbeiter" und nicht "Thread" enthält. Genau das, worüber wir sprechen, wird in der Dokumentation erwähnt, in der die Begriffe "Arbeitsthread" (Arbeitsthread) oder nur "Arbeiter" (Arbeiter) verwendet werden. In Zukunft werden wir den gleichen Ansatz verfolgen.

Wenn Sie bereits Multithread-Code geschrieben haben, werden Sie beim Erkunden der neuen Funktionen von Node.js viele Dinge sehen, mit denen Sie bereits vertraut sind. Wenn Sie zuvor noch nicht mit so etwas gearbeitet haben, lesen Sie einfach weiter, da hier entsprechende Erklärungen für Neuankömmlinge gegeben werden.

Über Aufgaben, die mit Hilfe von Mitarbeitern in Node.js gelöst werden können


Worker Flows sind, wie bereits erwähnt, dazu gedacht, Aufgaben zu lösen, die die Fähigkeiten des Prozessors intensiv nutzen. Es ist zu beachten, dass ihre Verwendung zur Lösung von E / A-Problemen eine Verschwendung von Ressourcen darstellt, da laut offizieller Dokumentation die internen Node.js-Mechanismen zur Organisation asynchroner E / A an sich viel effizienter sind als die Verwendung Lösung des gleichen Problems des Arbeiterflusses. Daher entscheiden wir sofort, dass wir uns nicht mit der Eingabe und Ausgabe von Daten unter Verwendung von Arbeitnehmern befassen.

Beginnen wir mit einem einfachen Beispiel, das zeigt, wie Worker erstellt und verwendet werden.

Beispiel Nr. 1


 const { Worker, isMainThread,  workerData } = require('worker_threads'); let currentVal = 0; let intervals = [100,1000, 500] function counter(id, i){   console.log("[", id, "]", i)   return i; } if(isMainThread) {   console.log("this is the main thread")   for(let i = 0; i < 2; i++) {       let w = new Worker(__filename, {workerData: i});   }   setInterval((a) => currentVal = counter(a,currentVal + 1), intervals[2], "MainThread"); } else {   console.log("this isn't")   setInterval((a) => currentVal = counter(a,currentVal + 1), intervals[workerData], workerData); } 

Die Ausgabe dieses Codes sieht aus wie eine Reihe von Zeilen, die Zähler zeigen, deren Werte bei unterschiedlichen Geschwindigkeiten ansteigen.


Die Ergebnisse des ersten Beispiels

Wir werden uns mit dem befassen, was hier passiert:

  1. Die Anweisungen im if Ausdruck erstellen 2 Threads, deren Code dank des Parameters __filename aus demselben Skript stammt, das Node.js beim Ausführen des Beispiels übergeben hat. Jetzt benötigen Worker den vollständigen Pfad zur Datei mit dem Code. Sie unterstützen keine relativen Pfade, weshalb dieser Wert hier verwendet wird.
  2. Daten an diese beiden Worker werden als globaler Parameter in Form des workerData Attributs workerData , das im zweiten Argument verwendet wird. Danach kann der Zugriff auf diesen Wert über eine gleichnamige Konstante erfolgen (achten Sie darauf, wie die entsprechende Konstante in der ersten Zeile der Datei erstellt wird und wie sie in der letzten Zeile verwendet wird).

Hier ist ein sehr einfaches Beispiel für die Verwendung des worker_threads Moduls. Hier passiert noch nichts Interessantes. Betrachten Sie daher ein anderes Beispiel.

Beispiel Nr. 2


Stellen Sie sich ein Beispiel vor, in dem wir erstens einige "schwere" Berechnungen durchführen und zweitens im Hauptthread etwas Asynchrones tun.

 const { Worker, isMainThread, parentPort, workerData } = require('worker_threads'); const request = require("request"); if(isMainThread) {   console.log("This is the main thread")   let w = new Worker(__filename, {workerData: null});   w.on('message', (msg) => { //  !       console.log("First value is: ", msg.val);       console.log("Took: ", (msg.timeDiff / 1000), " seconds");   })   w.on('error', console.error);   w.on('exit', (code) => {       if(code != 0)           console.error(new Error(`Worker stopped with exit code ${code}`))   });   request.get('http://www.google.com', (err, resp) => {       if(err) {           return console.error(err);       }       console.log("Total bytes received: ", resp.body.length);   }) } else { //    function random(min, max) {       return Math.random() * (max - min) + min   }   const sorter = require("./list-sorter");   const start = Date.now()   let bigList = Array(1000000).fill().map( (_) => random(1,10000))   sorter.sort(bigList);   parentPort.postMessage({ val: sorter.firstValue, timeDiff: Date.now() - start}); } 

Um dieses Beispiel auszuführen, beachten Sie, dass dieser Code das request benötigt (es kann mit npm installiert werden, z. B. mit den Befehlen npm init --yes und npm install request --save in einem leeren Verzeichnis mit der Datei, die den obigen Code enthält npm install request --save ) und die Tatsache, dass das Hilfsmodul verwendet wird, das durch den Befehl const sorter = require("./list-sorter"); . Die Datei dieses Moduls ( list-sorter.js ) sollte sich an derselben Stelle befinden wie die oben beschriebene Datei. Der Code sieht folgendermaßen aus:

 module.exports = {   firstValue: null,   sort: function(list) {       let sorted = list.sort();       this.firstValue = sorted[0]   } } 

Dieses Mal lösen wir gleichzeitig zwei Probleme. Erstens laden wir die google.com-Startseite und zweitens sortieren wir ein zufällig generiertes Array mit einer Million Zahlen. Dies kann einige Sekunden dauern, was uns eine großartige Gelegenheit bietet, die neuen Node.js-Mechanismen in Aktion zu sehen. Außerdem messen wir hier die Zeit, die der Worker-Thread benötigt, um die Zahlen zu sortieren. Danach senden wir das Messergebnis (zusammen mit dem ersten Element des sortierten Arrays) an den Hauptstrom, der die Ergebnisse in der Konsole anzeigt.


Das Ergebnis des zweiten Beispiels

In diesem Beispiel ist es am wichtigsten, den Mechanismus des Datenaustauschs zwischen Threads zu demonstrieren.
Dank der on Methode können Mitarbeiter Nachrichten vom Hauptthread empfangen. Im Code finden Sie die Ereignisse, die wir abhören. Das message wird jedes Mal message wenn wir eine Nachricht von einem bestimmten Thread mithilfe der parentPort.postMessage Methode parentPort.postMessage . Darüber hinaus kann dieselbe Methode verwendet werden, um eine Nachricht an einen Thread zu senden, indem auf eine Worker-Instanz parentPort und diese über das parentPort Objekt parentPort werden.

Schauen wir uns nun ein anderes Beispiel an, das dem, was wir bereits gesehen haben, sehr ähnlich ist, aber dieses Mal werden wir der Struktur des Projekts besondere Aufmerksamkeit schenken.

Beispiel Nr. 3


Als letztes Beispiel schlagen wir vor, die Implementierung der gleichen Funktionalität wie im vorherigen Beispiel in Betracht zu ziehen. Dieses Mal werden wir jedoch die Struktur des Codes verbessern, ihn übersichtlicher gestalten und in eine Form bringen, die den Komfort der Unterstützung eines Softwareprojekts erhöht.

Hier ist der Code für das Hauptprogramm.

 const { Worker, isMainThread, parentPort, workerData } = require('worker_threads'); const request = require("request"); function startWorker(path, cb) {   let w = new Worker(path, {workerData: null});   w.on('message', (msg) => {       cb(null, msg)   })   w.on('error', cb);   w.on('exit', (code) => {       if(code != 0)           console.error(new Error(`Worker stopped with exit code ${code}`))  });   return w; } console.log("this is the main thread") let myWorker = startWorker(__dirname + '/workerCode.js', (err, result) => {   if(err) return console.error(err);   console.log("[[Heavy computation function finished]]")   console.log("First value is: ", result.val);   console.log("Took: ", (result.timeDiff / 1000), " seconds"); }) const start = Date.now(); request.get('http://www.google.com', (err, resp) => {   if(err) {       return console.error(err);   }   console.log("Total bytes received: ", resp.body.length);   //myWorker.postMessage({finished: true, timeDiff: Date.now() - start}) //     }) 

Und hier ist der Code, der das Verhalten des __dirname + '/workerCode.js' beschreibt (im obigen Programm wird der Pfad zur Datei mit diesem Code mithilfe des __dirname + '/workerCode.js' ):

 const {  parentPort } = require('worker_threads'); function random(min, max) {   return Math.random() * (max - min) + min } const sorter = require("./list-sorter"); const start = Date.now() let bigList = Array(1000000).fill().map( (_) => random(1,10000)) /** //      : parentPort.on('message', (msg) => {   console.log("Main thread finished on: ", (msg.timeDiff / 1000), " seconds..."); }) */ sorter.sort(bigList); parentPort.postMessage({ val: sorter.firstValue, timeDiff: Date.now() - start}); 

Hier sind die Funktionen dieses Beispiels:

  1. Jetzt befindet sich der Code für den Hauptthread und für den Arbeitsthread in verschiedenen Dateien. Dies erleichtert die Unterstützung und Erweiterung des Projekts.
  2. Die Funktion startWorker gibt eine neue Instanz des Workers zurück, mit der bei Bedarf Nachrichten aus dem Hauptstrom an diesen Worker gesendet werden können.
  3. Es ist nicht erforderlich zu überprüfen, ob der Code im Hauptthread ausgeführt wird (wir haben die if mit der entsprechenden Überprüfung entfernt).
  4. Der Worker zeigt ein auskommentiertes Codefragment, das den Mechanismus zum Empfangen von Nachrichten vom Hauptstrom demonstriert, der angesichts des bereits diskutierten Mechanismus zum Senden von Nachrichten einen bidirektionalen asynchronen Datenaustausch zwischen dem Hauptstrom und dem Worker-Stream ermöglicht.

Zusammenfassung


In diesem Artikel haben wir anhand praktischer Beispiele die Funktionen der Verwendung der neuen Funktionen für die Arbeit mit Flows in Node.js untersucht. Wenn Sie das, was hier besprochen wurde, beherrschen, bedeutet dies, dass Sie bereit sind, Ihre Dokumentation einzusehen und Ihre eigenen Experimente mit dem Modul worker_threads . Vielleicht ist es erwähnenswert, dass diese Funktion nur in Node.js angezeigt wurde, obwohl sie experimentell ist. Im Laufe der Zeit kann sich daher etwas an ihrer Implementierung ändern. Wenn Sie bei Ihren eigenen Experimenten mit worker_threads auf Fehler stoßen oder feststellen, dass dieses Modul einige darin fehlende Funktionen nicht beeinträchtigt, worker_threads Sie die Entwickler darüber und helfen Sie bei der Verbesserung der Node.js.-Plattform.

Liebe Leser! Was halten Sie von Multithreading-Unterstützung in Node.js? Planen Sie, diese Funktion in Ihren Projekten zu verwenden?

Source: https://habr.com/ru/post/de415659/


All Articles