Race Condition einfach erklärt
Eine Race Condition entsteht, wenn mehrere Akteure auf eine gemeinsame Ressource zugreifen und das Ergebnis von ihrer zeitlichen Reihenfolge abhängt. Fehlt eine wirksame Koordination, können beispielsweise Änderungen verloren gehen oder Entscheidungen auf einem veralteten Zustand beruhen.
Auf dieser Seite lernst du, solche Wettlaufsituationen in Abläufen zu erkennen, ihre Ursache zu erklären und passende Gegenmaßnahmen auszuwählen.
Hake ab, was du schon kannst — und komm am Ende hierher zurück!
Woran erkennst du eine Race Condition?
Stell dir vor, zwei Kunden wollen gleichzeitig den letzten freien Kinoplatz kaufen. Beide sehen zunächst „frei“. Wenn Prüfung und Buchung nicht zusammen geschützt sind, können beide Käufe bestätigt werden.
Typischerweise kommen vier Merkmale zusammen:
- Mehrere Akteure, zum Beispiel Threads, Prozesse oder Dienste, arbeiten nebenläufig.
- Sie greifen auf dieselbe Ressource zu, etwa eine Variable, Datei oder einen Datensatz.
- Mindestens ein Zugriff verändert den Zustand.
- Die Zugriffe sind nicht ausreichend geordnet oder synchronisiert.
Nebenläufigkeit
Mehrere Abläufe machen im selben Zeitraum Fortschritt. Ihre einzelnen Schritte können sich zeitlich verschachteln. Sie müssen dafür nicht exakt gleichzeitig auf verschiedenen Prozessorkernen laufen.
Race Condition
Eine Race Condition ist eine Situation, in der das Ergebnis von der Reihenfolge oder dem Timing nebenläufiger Ereignisse abhängt.
Ein Data Race ist enger gefasst: Zwei Threads greifen gleichzeitig auf dieselbe Speicherstelle zu, mindestens einer davon schreibend. Nicht jede allgemeine Race Condition muss ein solcher gleichzeitiger Speicherzugriff sein. Auch ein getrenntes „prüfen, dann handeln“ kann eine Race Condition erzeugen.
Wie geht beim Read-Modify-Write ein Update verloren?
Viele scheinbar einfache Änderungen bestehen aus drei Schritten:
- Read: alten Wert lesen,
- Modify: neuen Wert lokal berechnen,
- Write: neuen Wert zurückschreiben.
Diese Folge heißt Read-Modify-Write. Wird sie unterbrochen, kann ein anderer Akteur denselben alten Wert lesen.
A und B sollen einen gemeinsamen Wert jeweils um 1 erhöhen. Der Ausgangswert ist 1. Nach zwei vollständigen Erhöhungen wird $1+1+1=3$ erwartet.
Geordneter Ablauf:
text A liest 1 → berechnet 2 → schreibt 2 B liest 2 → berechnet 3 → schreibt 3 Endwert: 3
Überlappender Ablauf:
text A liest 1 B liest 1 A berechnet lokal 2 B berechnet lokal 2 A schreibt 2 B schreibt 2 Endwert: 2
Beide Akteure berechnen aus demselben alten Wert die 2. Der letzte Schreibzugriff speichert deshalb ebenfalls 2. Eine Erhöhung ist verloren gegangen.
Kritischer Abschnitt
Ein kritischer Abschnitt ist der Teil eines Ablaufs, der auf gemeinsamen veränderlichen Zustand zugreift und deshalb vor störender Überlappung geschützt werden muss. Im Beispiel gehören Lesen, Erhöhen und Zurückschreiben gemeinsam dazu.
Nicht die Anzahl der ausgeführten Befehle entscheidet über den Endwert, sondern ihre mögliche Verschachtelung.
Warum ist Check-Then-Act ebenfalls gefährlich?
Beim Muster Check-Then-Act wird zuerst ein Zustand geprüft und danach aufgrund des Ergebnisses gehandelt. Zwischen diesen Schritten liegt ein Zeitfenster.
Ein Buchungssystem arbeitet so:
```text
`
- Prüfe: Ist Platz 42 frei?
- Wenn ja: Buche Platz 42.
Anfrage A prüft den Platz und erhält „frei“. Bevor A bucht, prüft auch Anfrage B und erhält ebenfalls „frei“. Beide handeln nun aufgrund derselben veralteten Beobachtung.
Die fachliche Regel „Ein Platz darf höchstens einmal vergeben werden“ kann verletzt werden, obwohl jede Anfrage für sich logisch aussieht.
Dieses Problem tritt auch bei Berechtigungsprüfungen, Dateien oder Kontoständen auf. Entscheidend ist stets: Darf sich der geprüfte Zustand vor der anschließenden Handlung noch ändern?
Wie schützt du den kritischen Abschnitt?
Es gibt nicht eine einzige Lösung für alle Systeme. Wähle die Maßnahme passend zur Ressource und zur geforderten Invariante. Eine Invariante ist eine Bedingung, die trotz nebenläufiger Abläufe erhalten bleiben muss, zum Beispiel: „Ein Platz wird höchstens einmal vergeben.“
Gemeinsamen Zustand vermeiden
Wenn Akteure getrennte, unveränderliche Daten verwenden, können sie sich nicht durch Schreibzugriffe auf denselben Zustand stören. Auch ein eindeutiger Besitzer einer Ressource kann Änderungen geordnet ausführen.
Operation atomar ausführen
Eine atomare Operation erscheint anderen Akteuren als unteilbare Einheit. Für den gemeinsamen Zähler muss die gesamte Änderung geschützt sein, nicht nur das Zurückschreiben.
Mit einer Sperre koordinieren
Ein Lock oder Mutex gewährt jeweils nur einem Akteur Zugang zum geschützten Abschnitt. Erst nach der Freigabe darf der nächste Akteur eintreten.
text Lock anfordern Wert lesen Wert erhöhen Wert schreiben Lock freigeben
Die Sperre muss bei allen Zugriffspfaden gelten, die dieselbe Invariante beeinflussen. Ein geschützter Schreibpfad hilft wenig, wenn ein anderer Pfad den Zustand ungeschützt liest und daraus eine Entscheidung ableitet.
Konflikte über Versionen erkennen
Bei einem Datensatz kann eine Aktualisierung nur dann angenommen werden, wenn seine Version noch der zuvor gelesenen Version entspricht. Hat ein anderer Akteur inzwischen geschrieben, wird der Konflikt erkannt. Der Vorgang kann den neuen Zustand lesen und kontrolliert wiederholt werden.
Synchronisierung hat Kosten: Zu große geschützte Bereiche können andere Akteure unnötig lange blockieren. Zu kleine Bereiche lassen zusammengehörige Schritte ungeschützt. Die richtige Granularität umfasst genau die Operationen, die gemeinsam die Invariante sichern müssen.
Wie findest und testest du Race Conditions?
Race Conditions sind oft schwer reproduzierbar. Last, Hardware und Ablaufplanung beeinflussen die Verschachtelung. Sogar zusätzliches Logging kann das Timing verändern und ein beobachtetes Symptom verschwinden lassen.
Ein sinnvolles Vorgehen ist:
- Gemeinsame veränderliche Ressourcen und alle Zugriffspfade erfassen.
- Die zu erhaltenden Invarianten benennen.
- Mögliche Lese-, Änderungs- und Schreibfolgen als Zeitabläufe notieren.
- Hohe Konkurrenz erzeugen und Timings kontrolliert variieren.
- Zugriffe mit Ressourcenkennungen protokollieren und die Reihenfolge rekonstruieren.
- Gefundene Fehler durch echte Koordination beheben und erneut testen.
Statische Analyse untersucht Code ohne Ausführung, kann aber auch vor Abläufen warnen, die praktisch keinen Fehler erzeugen. Dynamische Analyse beobachtet ausgeführte Abläufe, kann jedoch eine Race Condition übersehen, wenn genau die problematische Verschachtelung im Test nicht auftritt.
Ein erfolgreicher Test beweist nicht, dass keine Race Condition existiert. Er zeigt nur, dass der Fehler in den beobachteten Abläufen nicht aufgetreten ist.
sleep oder eine feste Verzögerung ist keine verlässliche Reparatur. Unter anderer Last kann sich die Reihenfolge erneut ändern.
Karteikasten
Überlege zuerst selbst und drehe die Karte anschließend zum Prüfen um.
Alles auf einen Blick
- Race Condition
- Voraussetzungen
- mehrere Akteure
- gemeinsame Ressource
- mindestens eine Änderung
- fehlende Koordination
- typische Muster
- Read-Modify-Write
- Check-Then-Act
- mögliche Folgen
- verlorene Updates
- inkonsistente Daten
- schwer reproduzierbare Fehler
- Gegenmaßnahmen
- Zustand vermeiden oder eindeutig besitzen
- atomare Operationen verwenden
- kritische Abschnitte sperren
- Versionskonflikte erkennen
- Prüfung
- Abläufe modellieren
- Konkurrenz und Timing variieren
- Invarianten kontrollieren
- Voraussetzungen
Abschluss-Check
Prüfe deinen Denkweg abschließend mit drei Fragen: Welche Ressource wird geteilt? Welche Schritte dürfen nicht störend verschachtelt werden? Welche Maßnahme erhält die geforderte Invariante?
Mit Google fortfahren