JS Multithreading

JS Multithreading

Stand: 07/2015
Lesedauer: ca. 4 Minuten

Inhalt:

  • setTimeout

  • Web Worker

  • BLOB Inline Worker

  • Eigener Worker

  • Einzeiliger Worker

  • Fazit

Entwicklung eines einfachen Multithreading-Mechanismus für JavaScript der ohne Aufwand an beliebigen Stellen in jedem Projekt genutzt werden kann.

setTimeout

Nebenläufigkeiten in JavaScript werden meist durch die setTimeout-Funktion realisiert. Hierfür kann eine gewisse Zeitspanne angegeben werden, wann der JavaScript-Code, welcher der Funktion übergeben wurde, ausgeführt wird. Zwar werden durch diese Methode unterschiedliche Ausführungsstränge erzeugt, allerdings laufen alle diese Ausführungsstränge auf dem gleichen Thread, was einem Time-Multiplexing und keiner echten Parallelität entspricht.

Web Worker

Für richtiges Multithreading werden die in JavaScript neu eingeführten Web Worker verwendet. Hierbei kann mithilfe der API ein Hintergrund-Skript erzeugt werden, welches als eigenständiger, nicht blockierender Thread läuft. Der Vorteil dieser Methode ist, dass jeder Thread auf einem eigenen CPU-Kern und somit echt parallel ausgeführt werden kann. So können rechenintensive Teile einer Webanwendung in einen nicht blockierenden und parallelen Bereich berechnet werden.

Um echte Parallelität zu erreichen müssen Web Worker eigenständigen, isolierten Code ausführen, welcher bei der Initialisierung eines Web Workers geladen wird.

var worker = new Worker('workerScript.js');

Jegliche Kommunikation zwischen dem Haupt-Thread und dem Worker läuft über ein internes Ereignisbasiertes Messaging-System. Bei Erhalt der ersten Nachricht über dieses System startet der Worker die Ausführung des JavaScript-Codes.

worker.postMessage();

Kommunikation

Wie im vorherigen Abschnitt erläutert, läuft die Kommunikation zwischen Workern und Haupt-Thread über das Nachrichtensystem. Hierfür muss sowohl der Haupt-Thread, als auch der Worker auf Nachrichten reagieren und diese verarbeiten. Für diesen Zweck müssen beide einen eigenen Event-Listener registrieren.

worker.addEventListener('message', function(e) {
  console.log(e.data); // prints the message
}, false);

und

self.addEventListener('message', function(e) {
  self.postMessage(e.data); // sends the message back
}, false);

Zugriffsmöglichkeiten

Aufgrund ihres Multithreading-Verhaltens können Web Worker nur auf einen Teil der Funktionen von JavaScript zugreifen:

  • navigator-Objekt
  • location-Objekt (schreibgeschützt)
  • XMLHttpRequest
  • setTimeout()
  • Anwendungscache
  • importScripts()
  • Erzeugen weiterer Web Worker

Worker haben keinen Zugriff auf:

  • DOM
  • window-Objekt
  • document-Objekt
  • parent-Objekt

BLOB Inline Worker

Soll der auszuführende JavaScript-Code nicht aus einer externen Datei geladen, sondern innerhalb des Main-JavaScript-Codes enthalten sein, ist dies durch die Erstellung und Übergabe eines BLOBs möglich.

Mit dem BlobBuilder kann der Worker und der Main-JavaScript-Codes in derselben Datei enthalten sein. Dazu muss lediglich ein BlobBuilder erstellt und diesem der Worker-Code übergeben werden:

var bb = new BlobBuilder();
bb.append("onmessage = function(e) {self.postMessage(e.data);}");

// creates a BLOB-URL
var blobURL = window.URL.createObjectURL(bb.getBlob());

var worker = new Worker(blobURL);

worker.onmessage = function(e) {
  console.log(e.data);
};

// start the worker
worker.postMessage();

Eigener Worker

Aus den oben gezeigten Beispielen zur Funktionalität der Web Worker wurde anschließend durch eine Reduzierung des Codes einer Worker entwickelt. Das folgende Beispiel erstellt einen neuen Worker, der Parallel jegliche Aufgabe abarbeiten kann, die ihm als JavaScript-Code übergeben wird. Der zu übergebende Code wird als Funktion behandelt. Dies bedeutet, dass beliebige JavaScript-Funktionalitäten angegeben und aufgerufen werden können. Der Scope ist vom Haupt-Thread hierbei komplett abgetrennt. Am Ende des Worker-Codes wird der return Aufruf verwendet, um das Ergebnis des Workers (wie eine normale Funktion) an den Main-Thread mittels Event-System zurückzugeben. Anschließend kann der Haupt-Thread das Ergebnis beliebig weiter verarbeiten.

// creates the content of the worker
var aFilePart = ["onmessage = function(e) {postMessage(eval('(function(){'+e.data+'})();'));}"];

// creates the BLOB and sets the type
var oMyBlob = new Blob(aFilePart, {type : 'text/html'});

// creates the URL for the BLOB
var blobURL = window.URL.createObjectURL(oMyBlob);

// creats the new web worker with the BLOB
var worker = new Worker(blobURL);

// creates the event listener for the main thread
// you have the change the content here!
worker.onmessage = function(e) {
  alert(e.data);
};

// a test parameter
var rounds = 10;

// sent the worker the JS-code to execute as a message
worker.postMessage("
  var ret = 'Hallo';
  for(var i=0; i<"+rounds+"; i++){
    ret += ' '+i;
  };
  return ret;
");

Einzeiliger Worker

Das folgenden Beispiel führt die Reduzierung weiter und zeigt das obige Beispiel in komprimierter einzeiliger Form:

Erstellung des BLOBs welcher für beliebig viele Worker verwendet werden kann.

var oMyBlob = new Blob(["onmessage = function(e) {postMessage(eval('(function(){'+e.data+'})();'));}"], {type : 'text/html'});

Erstellung eines Workers und dessen Event-Handler

var worker = new Worker(window.URL.createObjectURL(oMyBlob));
worker.onmessage = function(e) { // Event-Handler
  alert(e.data); // < - - CHANGE THIS CONTENT HERE!
};

Übergabe von beliebigem JavaScript-Code an den Worker mit gleichzeitigem Start des Workers.

worker.postMessage("JAVASCRIPT-CODE-HERE!");

Mit dieser Implementierung ist es möglich mehrere Web Worker zu erstellen und diese echt parallel Laufen zu lassen. Der BLOB muss hierfür nur einmal erstellt und jedem Worker bei dessen Erstellung übergeben werden. Ferner wird nicht für jede Ausführung ein neuer Worker benötigt. Ist der Worker fertig, so kann er mit einem erneuten Aufruf der postMessage()-Funktion mit neuem JavaScript-Code befüllt und die Ausführung gestartet werden. Die Ausführung des Codes wird sofort bei Erhalt der Nachricht gestartet.

Fazit

Mit der hier gezeigten Implementierung ist es nach der Initialisierung möglich, beliebigen JavaScript-Code an beliebigen Stellen einer Webanwendung mit nur einem Funktionsaufruf in einem echt parallel laufenden Thread abzuarbeiten.