System Design · Modul 2: Technische Fundamente

Lektion 2 von 2Text 15 Min.

Laufzeiten und Nebenläufigkeit

Lernziele
  • Prozesse, Threads und Event Loops unterscheiden
  • verstehen, warum Warten und Rechnen unterschiedlich behandelt werden müssen
  • die Stärken und Grenzen von Node.js, Python und anderen Laufzeiten einordnen
  • Wettlaufsituationen erkennen und mit Sperren oder atomaren Operationen verhindern

Ein Webserver bearbeitet selten nur eine Anfrage zur Zeit. Während ein Nutzer seine Kurse lädt, bucht ein anderer einen Termin und ein dritter lädt eine Datei hoch. Wie ein Programm viele Dinge gleichzeitig erledigt, bestimmt maßgeblich, wie viele Nutzer ein Server bedienen kann, wie schnell er antwortet und welche Fehler auftreten können. Dieses Thema heißt Nebenläufigkeit. Es klingt nach Betriebssystem-Theorie, hat aber sehr praktische Folgen für jeden Systementwurf – von der Wahl der Programmiersprache bis zur Frage, warum ein Kurs plötzlich überbucht ist.

1. Prozesse, Threads, Event Loop

Tab. 2.1Drei Arten, Arbeit gleichzeitig zu erledigen
ModellFunktionsweiseTypisch für
Mehrere Prozessevoneinander getrennte Programme mit eigenem SpeicherPython mit Gunicorn/Uvicorn-Workern, PHP-FPM
Mehrere Threadsparallele Ausführungsstränge in einem Prozess, gemeinsamer SpeicherJava, Go (Goroutinen), C#
Event Loopein Thread, der zwischen Aufgaben wechselt, sobald eine wartetNode.js, Python asyncio, Browser
Definition 2.1
Nebenläufigkeit und Parallelität
Nebenläufigkeit bedeutet, dass mehrere Aufgaben im selben Zeitraum voranschreiten, etwa indem ein Programm zwischen ihnen wechselt. Parallelität bedeutet, dass sie tatsächlich gleichzeitig auf mehreren Prozessorkernen ausgeführt werden. Ein Event Loop ist nebenläufig, aber nicht parallel.

Der entscheidende Unterschied liegt in der Art der Arbeit. Die meisten Webanwendungen verbringen den größten Teil ihrer Zeit mit Warten: auf die Datenbank, auf eine externe Schnittstelle, auf das Dateisystem, auf ein KI-Modell. Diese Wartezeiten nennt man I/O-gebunden. Andere Aufgaben erfordern echtes Rechnen: Bilder verkleinern, PDFs erzeugen, große Datenmengen auswerten. Sie sind CPU-gebunden. Je nach Art der Arbeit eignet sich ein anderes Modell.

2. Warten und Rechnen

Die Simulation zeigt eine Anfrage, die vier Aufgaben erledigen muss: drei, die vor allem warten, und eine, die rechnet. Vergleichen Sie die Modelle:

SimulationVier Aufgaben einer Anfrage – wie lange dauert es?
Nutzer laden
Kurse abfragen
Empfehlung (KI)
Bild verkleinern
warten (I/O)rechnen (CPU)

315 ms

Jede Aufgabe wartet auf die vorige – die Wartezeiten addieren sich.

Ein Event Loop ist hervorragend für Wartezeiten. Während die Datenbank antwortet, kann derselbe Thread schon die nächste Anfrage bearbeiten. Deshalb kann ein einziger Node.js-Prozess Tausende gleichzeitige Verbindungen halten, solange er vor allem wartet. Die Schwäche zeigt sich bei Rechenarbeit: Solange eine rechenintensive Aufgabe läuft, blockiert sie den einzigen Thread, und alle anderen Anfragen müssen warten. Ein einziger schlecht programmierter Endpunkt, der eine große Datei synchron verarbeitet, kann so einen ganzen Server lahmlegen.

Im Event Loop gilt: Niemals blockieren. Rechenintensive Arbeit gehört in einen Worker-Thread, einen eigenen Prozess oder – meist am besten – als Auftrag in eine Warteschlange, die von separaten Workern abgearbeitet wird.
Klick-GrafikWarten, ohne zu blockieren
Anfrage AAnfrage BEvent LoopDatenbank
Schritt 1/4 · A kommt an: Die Event Loop nimmt Anfrage A an und startet eine Datenbankabfrage.

3. Laufzeiten im Vergleich

Node.js basiert auf einem Event Loop und ist ideal für Webanwendungen und Schnittstellen mit vielen gleichzeitigen, überwiegend wartenden Anfragen. Für Rechenarbeit gibt es Worker Threads, in der Praxis werden solche Aufgaben aber meist ausgelagert. Python hat mit dem Global Interpreter Lock eine Besonderheit: In der Standardimplementierung führt immer nur ein Thread gleichzeitig Python-Code aus. Für Wartezeiten eignen sich asyncio und Frameworks wie FastAPI hervorragend, für Rechenarbeit mehrere Prozesse oder Bibliotheken, die intern in C rechnen. Neuere Python-Versionen experimentieren mit einer Variante ohne dieses Lock, die aber noch nicht Standard ist. Go und Java nutzen leichtgewichtige Threads und können Warten und Rechnen gleichermaßen gut parallelisieren.

Für den Systementwurf folgt daraus: Die Laufzeit prägt die Architektur. Eine Node.js- oder FastAPI-Anwendung wird typischerweise mit mehreren Prozessen pro Server betrieben, um alle Prozessorkerne zu nutzen, und schwere Arbeit wird in Hintergrund-Worker ausgelagert. Die Zahl der Prozesse, die Größe der Verbindungspools zur Datenbank und die Zeitlimits müssen zueinander passen – sonst warten Anfragen auf freie Verbindungen, obwohl der Server kaum ausgelastet ist.

4. Wettlaufsituationen

Definition 2.2
Race Condition
Ein Fehler, bei dem das Ergebnis davon abhängt, in welcher Reihenfolge nebenläufige Vorgänge zufällig ablaufen. Typisch ist das „Lost Update“: Zwei Vorgänge lesen denselben Wert, verändern ihn unabhängig und schreiben ihn zurück – eine der Änderungen geht verloren.

Nebenläufigkeit bringt eine Fehlerklasse mit sich, die besonders tückisch ist, weil sie nur selten und unter Last auftritt. Spielen Sie das klassische Beispiel durch: Zwei Menschen buchen gleichzeitig den letzten Platz in einem Yogakurs.

DemoZwei Buchungen für den letzten Platz im Kurs
  1. 1A liest: 1 freier Platz
  2. 2B liest: 1 freier Platz
  3. 3A schreibt: 0 frei, Buchung bestätigt
  4. 4B schreibt: 0 frei, Buchung bestätigt – überbucht!
Ohne Schutz: Beide lesen denselben Stand und überschreiben sich – ein klassisches „Lost Update“.

Die Lösung liegt fast immer in der Datenbank, nicht im Anwendungscode. Eine atomare Aktualisierung wie „verringere die freien Plätze um eins, aber nur, wenn sie größer als null sind“ prüft und ändert in einem einzigen Schritt. Alternativ sperrt eine Transaktion die betroffene Zeile, bis sie abgeschlossen ist. Eindeutige Regeln in der Datenbank, etwa dass ein Termin nur einmal vergeben werden kann, bieten eine letzte Sicherung. Prüfungen im Anwendungscode allein – „ist noch ein Platz frei? Dann buche!“ – reichen nicht, sobald mehrere Anfragen gleichzeitig laufen, und erst recht nicht, wenn mehrere Server beteiligt sind. Das Modul zur Datenhaltung vertieft Transaktionen und Isolationsstufen.

5. Weitere Fallen

Neben Race Conditions gibt es weitere typische Probleme. Ein Deadlock entsteht, wenn zwei Vorgänge jeweils auf eine Ressource warten, die der andere hält – beide warten ewig. Datenbanken erkennen das meist und brechen einen der Vorgänge ab; die Anwendung sollte dann erneut versuchen. Erschöpfte Pools entstehen, wenn alle Datenbankverbindungen belegt sind, etwa weil langsame Abfragen sie blockieren; neue Anfragen warten dann, bis ein Zeitlimit greift. Und unbegrenzte Parallelität kann ein System überlasten: Wer für jede eingehende Anfrage hundert parallele Aufrufe an einen externen Dienst startet, erzeugt bei Last eine Lawine. Begrenzungen, sogenannte Semaphoren oder Concurrency Limits, schützen davor.

Tab. 2.2Typische Nebenläufigkeitsprobleme
ProblemSymptomGegenmaßnahme
Race ConditionÜberbuchungen, falsche Zähleratomare Updates, Sperren, eindeutige Regeln
DeadlockAnfragen hängen, Datenbank bricht abfeste Reihenfolge beim Sperren, kurze Transaktionen, Wiederholen
Blockierter Event Loopalle Anfragen werden langsamRechenarbeit auslagern
Erschöpfter PoolWartezeiten trotz geringer CPU-LastPoolgröße anpassen, langsame Abfragen beheben, Zeitlimits
Unbegrenzte ParallelitätÜberlast bei AbhängigkeitenConcurrency Limits, Warteschlangen

6. Was das für den Entwurf bedeutet

Aus diesen Grundlagen ergeben sich einige Regeln, die in den folgenden Modulen immer wieder auftauchen. Anwendungsserver sollten zustandslos sein: Sie halten keine Daten im Arbeitsspeicher, die nach einem Neustart oder auf einem anderen Server fehlen würden. Dann lassen sich beliebig viele Instanzen parallel betreiben. Alles, was dauerhaft und für alle gleich sein muss, liegt in der Datenbank, und Regeln, die nie verletzt werden dürfen, werden dort durchgesetzt. Rechenintensive oder langsame Aufgaben gehen in Warteschlangen. Und jede Ressource – Verbindungen, parallele Aufrufe, Speicher – bekommt eine Obergrenze. Wer diese Regeln befolgt, baut Systeme, die unter Last vorhersehbar reagieren, statt auf überraschende Weise zu versagen. Im nächsten Modul geht es darum, wie Daten sicher und effizient gespeichert werden.

Ein praktischer Tipp zum Abschluss: Beobachten Sie in Ihrer eigenen Anwendung, wo Anfragen Zeit verbringen. Viele Frameworks und Überwachungswerkzeuge zeigen für jede Anfrage, wie lange sie auf die Datenbank, auf externe Dienste oder auf Rechenarbeit gewartet hat. Häufig zeigt sich, dass eine einzelne langsame Abfrage oder ein vergessenes Zeitlimit mehr Probleme verursacht als jede Frage nach der richtigen Programmiersprache. Die Wahl zwischen Node.js, Python oder Go ist selten entscheidend; entscheidend ist, dass man die Eigenschaften der gewählten Laufzeit kennt und die Architektur darauf abstimmt. Wer weiß, dass sein Event Loop nicht blockieren darf, dass Python-Threads nicht parallel rechnen und dass die Datenbank die letzte Instanz für Konsistenz ist, trifft in den folgenden Modulen bessere Entscheidungen – von der Wahl der Datenbank bis zur Gestaltung von Warteschlangen und Workern.

Quellen und weiterführende Literatur
  1. [1]Tanenbaum, A. S.; Bos, H. (2022): Modern Operating Systems. 5. Aufl. Hoboken: Pearson.
  2. [2]Node.js Foundation (2026): Don't Block the Event Loop (or the Worker Pool). nodejs.org.
  3. [3]Python Software Foundation (2024): PEP 703 – Making the Global Interpreter Lock Optional in CPython.
  4. [4]Kleppmann, M. (2017): Designing Data-Intensive Applications. Sebastopol: O'Reilly, Kap. 7 (Transactions).
  5. [5]PostgreSQL Global Development Group (2026): Documentation – Explicit Locking; Concurrency Control.

Stand: September 2026. Kursmaterial der Klarwerk Akademie. Zahlen zu Latenzen und Kosten sind Größenordnungen zur Orientierung, keine Messwerte.

Abschlussquiz

Drei Fragen – dann ist die Lektion geschafft.

Frage 1 von 3

Was blockiert einen Event Loop wie in Node.js?