Zum Inhalt springen

4.2 Koordination: Race Conditions und Worker

Zu Zen-Modus wechseln

Nach dieser Einheit kannst du:

  • Race Conditions (Doppelauslösung, überholte Antwort) erkennen und erklären, warum sie beim Testen nur selten auftreten.
  • eine Doppelauslösung mit einem In-Flight-Flag und eine überholte Antwort mit Abbruch oder latest wins absichern.
  • die Arbeitsteilung zwischen Event Loop (Warten) und Workern (Rechnen) erklären und begründen, warum Worker nur über Nachrichten kommunizieren.

Ein Gast im Lokal wird ungeduldig und bestellt sein Schnitzel ein zweites Mal, weil das erste noch nicht da ist. Die Küche kocht brav zwei, und auf der Rechnung stehen zwei. Im Webshop heißt dieser Gast: Doppelklick.

sequenceDiagram
    accTitle: Ein Doppelklick löst zwei Bestellungen aus
    participant user as Kundin
    participant shop as Webshop
    participant server as Server
    user->>shop: Klick auf "Bestellen"
    shop->>server: Bestellung 1
    user->>shop: zweiter Klick, 200 ms später
    shop->>server: Bestellung 2
    server-->>shop: OK (800 ms)
    server-->>shop: OK
    shop-->>user: zweimal bestellt, zweimal bezahlt

Abb. 4.5: Zwischen Klick und Antwort liegt eine Lücke, und ein zweiter Klick passt hinein (eigene Darstellung).

Einheit 4.1 hat gezeigt, wie du mehrere Abläufe gezielt kombinierst. Diese Einheit zeigt, was passiert, wenn sie sich ungewollt in die Quere kommen, und wie du echte Rechenarbeit aus dem einen Thread herausholst.

  • Race Condition: ein Fehler, der davon abhängt, in welcher Reihenfolge nebenläufige Abläufe fertig werden.
  • In-Flight-Flag: eine Variable, die sich merkt, dass eine Operation gerade unterwegs ist, damit keine zweite startet.
  • Latest wins: nur die Antwort auf die jüngste Anfrage wird übernommen, ältere werden verworfen.
  • Thread: eine eigene Ausführungsspur mit eigenem Call Stack. JavaScript hat pro Seite bzw. Prozess zunächst genau einen.
  • Worker: ein zusätzlicher Thread mit eigenem Event Loop, im Browser als Web Worker, in Node.js als Worker Thread.
// BUG: double-click means double order
orderButton.addEventListener("click", async () => {
await submitOrder(cart); // takes 800 ms; second click fits in here
showConfirmation();
});

Beispielprojekt: 04_nebenlaeufigkeit_koordination_examples/ex05_nogo_double_click

Zwischen Klick und Antwort liegen hunderte Millisekunden, und in diese Lücke passt ein zweiter Klick: Zwei Bestellungen laufen los, beide erfolgreich, die Kundin zahlt doppelt. Der Fehler ist tückisch, weil er beim Testen fast nie auftritt: Wer selbst testet, klickt einmal und wartet. Genau das ist typisch für Race Conditions. Die Standardabwehr ist ein In-Flight-Flag, dazu ein deaktivierter Knopf, damit die Sperre auch sichtbar ist:

let submitting = false;
orderButton.addEventListener("click", async () => {
if (submitting) return; // a request is already on its way
submitting = true;
orderButton.disabled = true;
try {
await submitOrder(cart);
showConfirmation();
} finally {
submitting = false;
orderButton.disabled = false;
}
});

Beispielprojekt: 04_nebenlaeufigkeit_koordination_examples/ex06_in_flight_flag

Im Browser getestet: Zwei Klicks direkt hintereinander ergeben im ersten Beispielprojekt zwei Bestellungen, im zweiten eine. Das finally gibt den Knopf auch dann wieder frei, wenn die Bestellung scheitert; sonst bliebe er nach einem Fehler für immer gesperrt.

Konkret heißt das: Jeder Knopf, der eine Anfrage an einen Server schickt, braucht eine Antwort auf diese Frage, ob “Bestellen”, “Speichern” oder “Like”. Beim Like ist ein doppelter Klick harmlos, bei der Bestellung kostet er Geld.

Die zweite häufige Race Condition entsteht bei einer Live-Suche: Nach jedem Buchstaben geht eine Anfrage hinaus, aber die Antworten kommen nicht zwingend in der Reihenfolge zurück, in der sie gesendet wurden. Die Antwort auf “ka” kann die auf “kam” überholen, und dann stehen veraltete Ergebnisse auf dem Bildschirm, obwohl im Suchfeld “kam” steht.

sequenceDiagram
    accTitle: Die Antwort auf eine ältere Anfrage kommt zuletzt
    participant ui as Suchfeld
    participant server as Server
    ui->>server: suche "ka"
    ui->>server: suche "kam"
    server-->>ui: Ergebnis "kam" (schnell)
    server-->>ui: Ergebnis "ka" (langsam), überschreibt "kam"

Abb. 4.6: Die langsamere Antwort auf die ältere Anfrage kommt zuletzt und gewinnt, wenn niemand aufpasst (eigene Darstellung).

Zwei Abwehrmuster: Entweder du brichst alte Anfragen mit einem AbortController ab (Einheit 4.1), oder du zählst mit und übernimmst nur die Antwort auf die jüngste Anfrage (latest wins):

let latestRequest = 0;
async function onInput(term) {
const request = ++latestRequest;
const results = await search(term);
if (request !== latestRequest) return; // a newer request is on its way
showResults(results);
}

Beispielprojekt: 04_nebenlaeufigkeit_koordination_examples/ex07_latest_wins

Im Beispielprojekt braucht die Suche nach “k” am längsten, die nach “kam” am kürzesten; angezeigt wird trotzdem nur results for "kam". Der Unterschied zum Abbruch: Latest wins lässt die alten Anfragen zu Ende laufen und ignoriert nur ihr Ergebnis, der Abbruch spart zusätzlich Netz und Server-Zeit.

Wo begegnet dir das? In jeder Suchleiste mit Vorschlägen beim Tippen, etwa in einem Online-Shop oder einer Karten-App. Wenn dort nach schnellem Tippen kurz falsche Vorschläge aufblitzen, fehlt genau diese Absicherung.

Auflösung

Beide Klicks schicken ein Like, und die Anzeige steigt um zwei, obwohl ein Benutzer nur einmal liken darf. Es ist eine Doppelauslösung, also hilft das In-Flight-Flag: Solange das erste Like unterwegs ist, wird der zweite Klick ignoriert und der Knopf ist deaktiviert. Latest wins passt nicht, weil hier nicht die jüngste Antwort, sondern die doppelte Aktion das Problem ist.

Der Event Loop überbrückt Wartezeiten, aber er hilft nicht gegen Rechenzeit: Eine Funktion, die zehn Sekunden rechnet (Bildfilter, Videoanalyse, große Sortierung), blockiert den einen Thread vollständig, samt Oberfläche. Im Lokal: Der Kellner kann nicht nebenbei ein Menü für hundert Gäste kochen. Dafür holt man einen zweiten Koch in eine eigene Küche. Für diesen Fall bietet JavaScript Worker: eigenständige Threads mit eigenem Event Loop. Damit landet das Lehrplan-Thema Threading in der Praxis.

// main.js: start the worker, send work, receive the result
const worker = new Worker("histogram-worker.js");
worker.postMessage({ pixels });
worker.addEventListener("message", (event) => {
drawHistogram(event.data.histogram);
});
// histogram-worker.js: runs in its own thread
self.addEventListener("message", (event) => {
const histogram = computeHistogram(event.data.pixels); // heavy work
self.postMessage({ histogram });
});

Beispielprojekt: 04_nebenlaeufigkeit_koordination_examples/ex08_web_worker

Im Beispielprojekt zählt der Worker die Grauwerte von 20 Millionen Pixeln, und eine Uhr auf der Seite tickt währenddessen alle 100 Millisekunden weiter: Der Hauptthread bleibt bedienbar.

Die entscheidende Design-Entscheidung: Worker teilen keinen Speicher mit dem Hauptthread. Kommunikation läuft ausschließlich über Nachrichten (postMessage), deren Daten kopiert werden. Die beiden Köche reden nur über Bestellzettel, keiner greift in die Töpfe des anderen.

flowchart LR
    accTitle: Hauptthread und Worker tauschen nur Nachrichten aus
    main["Hauptthread:<br/>Oberfläche, Event Loop"] -- "postMessage(pixels)<br/>Kopie" --> worker["Worker:<br/>eigener Thread"]
    worker -- "postMessage(histogram)<br/>Kopie" --> main

Abb. 4.7: Hauptthread und Worker teilen keine Variablen, sie schicken sich Kopien (eigene Darstellung).

Das macht Worker etwas umständlich, erspart JavaScript aber die klassischen Probleme geteilten Speichers, die Threading in anderen Sprachen wie Java, C++ oder Python berüchtigt machen. Dort können mehrere Threads dieselben Variablen gleichzeitig ändern, und ohne Absicherung mit Sperren (Locks) entstehen Race Conditions im Speicher und Verklemmungen (Deadlocks), bei denen zwei Threads ewig aufeinander warten.

Damit ergibt sich eine klare Arbeitsteilung:

  • Warten (Netzwerk, Dateien, Timer): Event Loop mit async/await; ein Thread genügt, Worker wären nur Aufwand.
  • Rechnen (CPU-lastige Arbeit von spürbarer Dauer): Worker, damit der Hauptthread bedienbar bleibt.

Wo begegnet dir das? Bildbearbeitung im Browser, etwa ein Online-Werkzeug zum Zuschneiden oder Filtern von Fotos: Die Rechenarbeit läuft in einem Worker, damit die Regler beim Ziehen nicht ruckeln.

Auflösung

Nur (b): Der Weichzeichner rechnet viele Sekunden lang und würde den Hauptthread blockieren. (a) und (c) sind Warten: Während der Server antwortet oder die Festplatte liest, ist der Thread frei, und async/await genügt. Ein Worker wäre dort nur zusätzlicher Aufwand.

  1. Knopf nur optisch deaktiviert: disabled allein schützt nicht vor Aufrufen, die nicht über den Knopf kommen, etwa einer Tastenkombination. Das In-Flight-Flag im Code ist die eigentliche Sperre.
  2. Flag ohne finally: Scheitert die Anfrage, bleibt submitting auf true, und der Knopf ist für immer gesperrt.
  3. “Bei mir tritt der Fehler nie auf”: Race Conditions hängen vom Timing ab. Langsame Verbindungen, Last am Server oder schnelle Doppelklicks lösen sie aus; im Test hilft eine künstlich verlängerte Wartezeit.
  4. Worker für Wartezeiten: Ein Worker beschleunigt keine Netzwerkanfrage. Er lohnt sich nur für Rechenarbeit.
  5. Gemeinsame Variable zwischen Hauptthread und Worker erwartet: Worker sehen die Variablen des Hauptthreads nicht; alles läuft über Nachrichten mit Kopien.

Du kannst dir diese Einheit kostenlos mit der Vorlesefunktion deines Browsers oder Betriebssystems vorlesen lassen (etwa „Laut vorlesen” in Edge), ohne Konto und ohne Upload; die Codebeispiele liest du dabei am Bildschirm mit. Freiwillig kannst du mit Google NotebookLM (Google-Konto nötig) aus diesem öffentlichen Text eine gesprochene Zusammenfassung erzeugen, auch auf Deutsch.

Du kannst jetzt:

  • Race Conditions (Doppelauslösung, überholte Antwort) erkennen und erklären, warum sie beim Testen nur selten auftreten.
  • eine Doppelauslösung mit einem In-Flight-Flag und eine überholte Antwort mit Abbruch oder latest wins absichern.
  • die Arbeitsteilung zwischen Event Loop (Warten) und Workern (Rechnen) erklären und begründen, warum Worker nur über Nachrichten kommunizieren.
  • Aufgabe 04-2a - Der Wettlauf
  • Aufgabe 04-1a - Parallel oder nacheinander?, Stufe MandalorianPlus (Worker)
  • Race Condition, Begriff → Wikipedia: Race condition
  • Web Worker, postMessage, eigener Event Loop → MDN: Using Web Workers, Worker: postMessage()
  • Daten werden beim Senden kopiert → MDN: The structured clone algorithm
  • Worker Threads in Node.js → Node.js-Dokumentation: Worker threads
  • Lehrplanbezug (Threading) → Lehrplan der Höheren Lehranstalt für Informationstechnologie, B.2 Medientechnik, 2.1 Softwareentwicklung, IV. Jahrgang (zitiert auf der Kursseite Lehrplan und Ablauf)
  • Verhalten der Beispielprojekte (zwei gegen eine Bestellung, latest wins, Uhr läuft während des Workers) → eigener Test in Chrome und Node.js 24.11.1 (Oktober 2026)
  • Abb. 4.5 (Doppelklick), Abb. 4.6 (überholte Antwort), Abb. 4.7 (Hauptthread und Worker) → eigene Darstellung, alle als Mermaid-Diagramm
  • Oliver Maurhart (MAO)
  • Michael Gabriel (GAM)

CC-BY-SA

https://qiz.at/rules