JavaScript Web Worker: Ein Anfänger's Guide
Im Jahr 2019 hat sich das Web-Ökosystem so weit entwickelt, dass der Browser eine Ausführungsumgebung für Anwendungen ist, die auf JavaScript basieren. Dies spiegelt sich in der Geschwindigkeit wider, mit der die Branche Jahr für Jahr neue Frameworks, Paradigmen, Modullader und Bundler, Abhängigkeitsmanager, Build-Tools und Paketmanager entwickelt.
Als JavaScript in den frühen Tagen des Internets konzipiert wurde, war die Richtung der Internetentwicklung nicht klar. Aufgrund des ständigen, schnellen Wandels in der Branche und im Ökosystem, der Notwendigkeit der Abwärtskompatibilität von Browsern und Webstandards, wurde die Entwicklung von JavaScript zu einem konstanten Strom von Patches, Hacks und Nachgedanken.
Die heutigen mobilen Geräte verfügen normalerweise über mehr als 8 CPU-Kerne oder mehr als 12 GPU-Kerne. Desktop- und Server-CPUs haben bis zu 16 Kerne, 32 Threads oder mehr.
In dieser Umgebung ist eine dominante Programmier- oder Skriptumgebung, die Single-Threaded ist, ein Engpass.
JavaScript ist Single-threaded
Dies bedeutet, dass JavaScript—Engines — ursprünglich Browser – konstruktionsbedingt einen Hauptausführungsthread haben und, um es einfach auszudrücken, Prozess oder Funktion B erst ausgeführt werden kann Prozess oder Funktion A ist fertig. Die Benutzeroberfläche einer Webseite reagiert nicht auf andere JavaScript—Verarbeitungen, während sie mit der Ausführung von etwas beschäftigt ist – dies wird als DOM-Blockierung bezeichnet.
Das ist furchtbar ineffizient, besonders im Vergleich zu anderen Sprachen.
Wenn wir zu JS Bin gehen und diesen Code in der JavaScript-Konsole des Browsers ausführen:
//noprotecti = 0;while (i < 60000) { console.log("The number is " + i); i++;}
… das ganze jsbin.com die Website reagiert nicht mehr, bis der Browser 60.000 zählt und protokolliert.
Wir können mit nichts auf der Seite interagieren, da der Browser ausgelastet ist.
Dies ist ein relativ anspruchsloser Rechenprozess, und die heutigen Web-Apps beinhalten oft viel anspruchsvollere Aufgaben.
Wir müssen in der Lage sein, Dinge im Hintergrund zu berechnen, während der Benutzer nahtlos mit der Seite interagiert.
Web Workers
Das W3C veröffentlichte 2009 einen ersten Entwurf des Web Workers Standards. Die vollständige Spezifikation finden Sie auf der Website der Web Hypertext Application Technology Working Group — oder WHATWG — einer Alternative zu W3C für Webstandards.
Web Workers ist ein asynchrones System oder Protokoll, mit dem Webseiten Aufgaben im Hintergrund ausführen können, unabhängig vom Hauptthread und der Benutzeroberfläche der Website. Es ist eine isolierte Umgebung, die vom window
-Objekt, dem document
-Objekt, dem direkten Internetzugang isoliert ist und sich am besten für lang andauernde oder anspruchsvolle Rechenaufgaben eignet.
Abgesehen von Web Workers — einem System für Multithreading — gibt es andere Möglichkeiten, eine asynchrone Verarbeitung in JavaScript zu erreichen, z. B. asynchrone Ajax-Aufrufe und Ereignisschleifen.
Um dies zu demonstrieren, gehen wir zurück zu JS Bin und versuchen dieses Snippet:
console.log("A");setTimeout(function(){console.log("B");},2000);console.log("C");setTimeout(function(){console.log("D");},0);console.log("E");setTimeout(function(){console.log("F");},1000);
Wenn wir dies ausführen, lautet unsere Protokollsequenz A, C, E, D, F, B
. Der Browser plant zuerst Operationen ohne das Timeout, wie sie kommen, und führt dann die setTimeout()
Funktionen in der Reihenfolge ihrer jeweiligen angegebenen Verzögerungen aus. Diese Asynchronität sollte jedoch nicht automatisch mit Multithreading zusammengeführt werden. Abhängig von der Host-Maschine kann dies oft nur ein Single-Thread-Stack der Aufrufe in der von uns erläuterten Reihenfolge sein.
Web Worker & Multithreading
Wie Mozillas JavaScript-Referenzwebsite erklärt, sind Web Worker ein „Mittel für Webinhalte, um Skripte in Hintergrundthreads auszuführen.“
Wir verwenden sie auf folgende Weise: wir prüfen die Verfügbarkeit des Worker()
Konstruktors im Browser und instanziieren, falls verfügbar, ein Worker-Objekt mit der Skript-URL als Argument. Dieses Skript wird in einem separaten Thread ausgeführt.
Das Skript muss aus Sicherheitsgründen von demselben Host oder derselben Domäne bereitgestellt werden, und das ist auch der Grund, warum Web Worker nicht funktionieren, wenn wir die Datei lokal mit einem file://
Schema öffnen.
if (typeof(Worker) !== "undefined") { worker = new Worker("worker.js");}
Nun definieren wir diesen Code in der worker.js
Datei:
i = 0;while (i < 200000) { postMessage("Web Worker Counter: " + i); i++;}
Wenn Sie hochwertige JavaScript-Webworker-Dateien schreiben möchten, lesen Sie unser Buch JavaScript: Best Practice.
Die Trennung von Threads
Eine wichtige Sache, die hier zu beachten ist, ist die Trennung des window
und document
Ausführungsbereichs im Hauptfenster des Browsers Thread und der worker
Umfang.
Um den worker
Thread nutzen zu können, müssen diese beiden Bereiche kommunizieren können. Um dies zu erreichen, verwenden wir die postMessage()
Funktion innerhalb der worker.js
Datei — um Nachrichten an den Haupt—Browser-Thread zu senden – und den worker.onmessage
Listener im Haupt-Thread, um worker
Nachrichten abzuhören.
Wir können auch Nachrichten vom Haupt-Browser-Thread an den worker
Thread oder die Funktion senden. Der einzige Unterschied besteht darin, dass wir die Dinge umkehren und worker.postMessage()
im Hauptthread und onmessage
im Arbeitsthread aufrufen. Um Mozillas Entwicklerreferenz zu zitieren:
Beachten Sie, dass
onmessage
undpostMessage()
vomWorker
Objekt abgehängt werden müssen, wenn es im Hauptskriptthread verwendet wird, nicht jedoch im Worker. Dies liegt daran, dass der Worker innerhalb des Worker effektiv der globale Scope ist.
Wir können die terminate()
Methode auf die gleiche Weise verwenden, um die Ausführung unseres Arbeiters zu beenden.
In diesem Sinne kommen wir zu diesem Beispiel:
index.html
<!DOCTYPE html><html><head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <title>Web Workers Example</title> <style type="text/css"> body {padding-top:28px;} .output-cont {margin-left:12%; margin-top:28px;} .output-cont h3 {width:200px; height:100%;} .output-cont button {padding:4px 8px; font-size:1.1rem; font-family:sans-serif; } </style></head><body><div class="output-cont"><button onclick="testWorker()">start worker</button><h3></h3><button onclick="terminateWorker()">terminate worker</button></div><br/><div class="output-cont"><button onclick="testMainThread()">start blocking thread</button><h3></h3></div><br/><div class="output-cont"><button onclick="alert('browser responsive!')">test browser responsiveness</button></div> <script> var worker; function testWorker() { if (typeof(Worker) !== "undefined") { if (typeof(worker) == "undefined") { worker = new Worker("worker.js"); } worker.onmessage = function(event) { document.getElementById("workerOutput").innerHTML = event.data; }; } else { document.getElementById("workerOutput").innerHTML = "Web Workers are not supported in your browser"; } } function terminateWorker() { worker.terminate(); worker = undefined; } function testMainThread() { for (var i = 0; i < 200000; i++) { document.getElementById("mainThreadOutput").innerHTML = "Main Thread Counter: " + i; } } </script></body></html>
und Arbeiter.js:
i = 0;while (i < 200000) { postMessage("Web Worker Counter: " + i); i++;}
Dies gibt uns die Möglichkeit, die Auswirkungen der Ausführung des Hauptthreads auf das Verhalten und die Leistung der Seite im Vergleich zu den Auswirkungen des Webworkers zu testen.
In diesem Tutorial haben wir http-server
verwendet, um die Dateien lokal bereitzustellen.
Jetzt können wir sehen, dass der Arbeitsthread die Interaktivität des Hauptbrowserprozesses nicht blockiert und das Durchlaufen von 200.000 Nummern den Hauptthread nicht beeinflusst. Die Zahlen im #workerOutput
-Element werden bei jeder Iteration aktualisiert.
Der blockierende Thread oder Hauptthread blockiert, wenn er in einer Schleife ausgeführt wird, die gesamte Interaktivität (wir haben hier die Anzahl der Iterationen auf 200.000 festgelegt, aber es wird noch offensichtlicher, wenn wir ihn auf 2.000.000 erhöhen).
Eine weitere Sache, die uns auf einen blockierten Hauptthread hinweist, ist, dass der Worker-Prozess die Seite bei jeder Iteration aktualisiert und die Schleife im Hauptthread (die in index.html
) nur das #mainThreadOutput
Element bei der letzten Iteration aktualisiert.
Dies liegt daran, dass der Browser zu sehr mit dem Zählen beschäftigt ist (for
Schleife), um das DOM neu zeichnen zu können, also tut er es nur einmal sein Geschäft mit der for
Schleife ist vollständig erledigt (am Ende der Schleife).
Fazit
In diesem Artikel haben wir Web Worker vorgestellt, eine Technologie, die der Webbranche hilft, mit immer anspruchsvolleren Web-Apps Schritt zu halten. Dies geschieht, indem Web-Apps die Möglichkeit erhalten, Multiprozessor- und Multithread-Geräte zu nutzen, indem JavaScript einige Multithread-Superkräfte verliehen werden.
Web Worker verwandeln die mobilen und Desktop-Browserumgebungen in Anwendungsplattformen und bieten ihnen eine strikte Ausführungsumgebung. Diese Strenge kann uns zwingen, das Kopieren von Objekten zwischen mehreren Threads vorzusehen und unsere Anwendungen unter Berücksichtigung dieser Einschränkungen zu planen.