Concurrency model and Event Loop

von 2 Mitwirkenden:

Dieser Artikel benötigt eine technische Überprüfung.

Dieser Artikel benötigt eine redaktionelle Überprüfung.

Diese Übersetzung ist unvollständig. Bitte helfen Sie, diesen Artikel aus dem Englischen zu übersetzen.

JavaScript hat ein Gleichzeitigkeitsmodell (concurrency model), welches auf einem "Event Loop" basiert.  Dieses Modell unterscheidet sich stark von Modellen ander Sprachen wie C oder Java.

Laufzeitkonzepte (Runtime concepts)

Die folgenden Sektionen erklären ein theoretisches Model. Moderne JavaScript-Engines implementieren und optimieren die beschriebenen Semantiken stark.

Schematisches, visuelles Beispiel

Stack, heap, queue

Stack (Stapel)

Funktionsaufrufe erzeugen einen Stack von Frames.

function f(b){
  var a = 12;
  return a+b+35;
}

function g(x){
  var m = 4;
  return f(m*x);
}

g(21);

Beim Aufruf der Funktion g wird das erste Frame erstellt und auf dem Stapel abgelegt. Das Frame enthält g's Argumente und lokale Variablen. Sobald g die Funktion f  aufruft, wird ein zweiter Frame erstellt und an oberster Stelle auf dem Stapel abgelegt. Das "f-Frame" enhält die die Argumente von f und dessen lokalen Variablen. An dem Stelle wo f einen Wert zurückgibt (return a+b+35) , wird das oberste Frame-Element vom Stapel genommen, so dass nur noch das g-Frame auf dem Stapel ist. Sobald die Funktion g etwas zurückgibt, ist der Stapel wieder leer.

Heap (Haufen)

Objekte werden in einem Heap reserviert. Als Heap wird eine hauptsächlich unstrukturierte Region des Speichers bezeichnet.

Queue (Warteschlange)

Eine JavaScript Runtime enthält eine Message Queue (Nachrichtenwarteschlange), welche eine Liste an Nachrichten abarbeitet. Jede Nachricht steht mit einer Funktion im Zusammenhang. Wenn der Stack leer ist, wird eine Nachricht aus der Schlange entnommen und verarbeitet. Beim Verarbeiten wird die im Zusammenhang stehende Funktion aufgerufen wodurch ein erstes Stack-Frame erzeugt wird. Die Verarbeitung der Nachricht endet sobald der Stack erneut leer ist.

Event loop (Ereignisschleife)

Der event loop erhielt seinen Namen vermutlich durch die Art, wie dieser meist implementiert wird (als Wiederholungen):

while (queue.waitForMessage()){
  queue.processNextMessage();
}

Sollten zu einem Zeitpunkt keine Nachrichten vorhanden sind, wartet queue.waitForMessage synchron auf eingehende Nachrichten.

"Run-to-completion" (Ausführen bis zur Fertigstellung)

Jede Nachricht wird vollständig abgearbeitet bevor irgendeine weitere Nachricht verarbeitet wird. Dies bietet ein paar schöne Eigenschaften bei der Planung eines Programms: Wenn eine Funktion ausgeführt wird, kann dieser nichts zuvorkommen und die Funktion wird daher vollständig ausgeführt bevor irgendein anderer Code ausgeführt wird, der gegebenenfalls die Daten ändert, die die Funktion manipulieren soll. Dies untescheidet sich z.B. von C, wo eine Funktion in einem Thread läuft und gestoppt werden kann, um anderen anderen Code in einem anderen Thread auszuführen.

Dies bringt aber auch folgenden Nachteil mit sich: Braucht eine Nachricht extrem lange um fertig verarbeitet zu werden, kann währenddessen die Webanwendung auf keine Nutzer-Interaktionen, wie z.B. Klicken oder Scrollen, reagieren. Der Browser entschärft dies mit dem "a script is taking too long to run" Dialog. Gute Praxis ist es daher die Dauer der Nachrichtenverarbeitung kurz zu halten und wenn möglich in viele einzelne Nachrichten aufzuteilen.

Hinzufügen von Nachrichten

In einem Webbrowser werden Nachrichten immer hinzugefügt, wenn ein Event auftritt und diesem ein Event Listener hinzugefügt wurde. Ist kein Listener hinzugefügt, so ist das Event verloren. So würde z.B. ein Klick auf ein Element eine Nachricht erzeugen, falls diesem ein Klick-Event Listener hinzugefügt wurde .

Ein Aufruf der Funktion setTimeout(callback, timeToWait) hängt der Queue eine Nachricht an nachdem die im zweiten Argument angegebene Zeit vergangenen ist. Existiert keine andere Nachricht in der Schlange, so wird die Callback-Funktion unmittelbar ausgeführt;  wenn in der Schlange hingegen viele Nachrichten vorhanden sind, wird die setTimeout Nachricht erst verarbeitet sobald alle vorherigen Nachrichten verarbeitet wurden. Aus diesem Grund gibt das zweite Argument immer nur die Mindestwartezeit und nicht die exakte Wartezeit an.

Ein Web Worker oder ein Cross-Origin iFrame haben ihren eigenen Stack, ihren eigenen Heap und ihre eigene Message Queue. Zwei unabhängige Laufzeiten können nur durch das Senden von nachrichten über die postMessage Methode kommunizieren. Diese Methode fügt der Message Queue der anderen Laufzeit eine Nachricht hinzu, wenn diese auf die Message-Events hört (listens).

Never blocking (niemals blockieren)

Eine sehr interessante Eigenschaft des Event Loop Modells ist es, dass Javascript, im Gegensatz zu vielen anderen Sprachen, niemals blockierend ist. Die Handhabung von I/O wird typischerweise über Events und Callback-Funktionen erledigt. Dies ermöglicht einer Applikation weiterhin auf andere Dinge, wie z.B. Nutzer-Eingaben, zu reagieren,  auch wenn sie selber noch auf das Ergebnis einer IndexedDB- oder XHR-Anfrage wartet.

Es gibt Ausnahmen, wie z.B. alert oder synchrone XHR, wobei es eine gute Praxis ist, diese zu vermeiden. Obacht, es existieren Ausnahmen für die Ausnahme (aber diese sind für gewöhnlich vielmehr Bugs bei der Implementierung als irgendetwas anderes).

Schlagwörter des Dokuments und Mitwirkende

Mitwirkende an dieser Seite: clemens, Broetchen1234
Zuletzt aktualisiert von: clemens,
Seitenleiste ausblenden