4.2 Koordination: Race Conditions und Worker
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.
Begriffe vorab
Abschnitt betitelt „Begriffe vorab“- 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.
Race Conditions
Abschnitt betitelt „Race Conditions“Die Doppelauslösung
Abschnitt betitelt „Die Doppelauslösung“// BUG: double-click means double orderorderButton.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 überholte Antwort
Abschnitt betitelt „Die überholte Antwort“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.
Threading: Worker
Abschnitt betitelt „Threading: Worker“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 resultconst worker = new Worker("histogram-worker.js");worker.postMessage({ pixels });worker.addEventListener("message", (event) => { drawHistogram(event.data.histogram);});// histogram-worker.js: runs in its own threadself.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.
Stolperfallen
Abschnitt betitelt „Stolperfallen“- Knopf nur optisch deaktiviert:
disabledallein schützt nicht vor Aufrufen, die nicht über den Knopf kommen, etwa einer Tastenkombination. Das In-Flight-Flag im Code ist die eigentliche Sperre. - Flag ohne
finally: Scheitert die Anfrage, bleibtsubmittingauftrue, und der Knopf ist für immer gesperrt. - “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.
- Worker für Wartezeiten: Ein Worker beschleunigt keine Netzwerkanfrage. Er lohnt sich nur für Rechenarbeit.
- Gemeinsame Variable zwischen Hauptthread und Worker erwartet: Worker sehen die Variablen des Hauptthreads nicht; alles läuft über Nachrichten mit Kopien.
Zusatzmaterialien
Abschnitt betitelt „Zusatzmaterialien“- MDN: Using Web Workers - Worker anlegen, Nachrichten senden, Fehler behandeln.
- MDN: The structured clone algorithm - welche Daten
postMessagekopieren kann und welche nicht. - Node.js-Dokumentation: Worker threads - dasselbe Modell auf dem Server, mit
node:worker_threads. - MDN: AbortController - das zweite Abwehrmuster gegen überholte Antworten.
Zum Anhören
Abschnitt betitelt „Zum Anhören“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.
Lernergebnisse
Abschnitt betitelt „Lernergebnisse“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.
Passende Übungen
Abschnitt betitelt „Passende Übungen“- Aufgabe 04-2a - Der Wettlauf
- Aufgabe 04-1a - Parallel oder nacheinander?, Stufe MandalorianPlus (Worker)
Quellen
Abschnitt betitelt „Quellen“- 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
Autoren
Abschnitt betitelt „Autoren“- Oliver Maurhart (MAO)
- Michael Gabriel (GAM)
CC-BY-SA