Informatik · Klasse 12 · aktualisiert
Kritischer Abschnitt: Race Conditions verstehen
Du lernst, warum mehrteilige Änderungen an einem Zähler schiefgehen können, was eine Race Condition ist und welche Regeln eine Lösung erfüllen muss.
0 von 10 Aufgaben gelöst
Kurz gesagt
Ein kritischer Abschnitt ist ein Stück Programm aus mehreren Anweisungen, in dem sich zu einer Zeit nur ein Thread aufhalten darf. Sonst kann eine Race Condition entstehen: Das Ergebnis hängt vom zeitlichen Ablauf ab und kann falsch sein. Dagegen hilft wechselseitiger Ausschluss.
Stell dir (ausgedachtes Beispiel) vor, zwei Threads zählen gemeinsam Besucher einer Webseite. Ohne Schutz kann am Ende eine Zahl fehlen, obwohl jeder Thread für sich richtig arbeitet. Das kann passieren, wenn mehrere Threads dieselbe Variable ändern und sich dabei überschneiden. Hier lernst du, warum das so ist und was dagegen hilft.
Deine Lernziele
Hake ab, was du schon kannst. Löst du alle Aufgaben eines Abschnitts, hakt Mela das Ziel für dich ab.
4 Abschnitte
Ein Thread ist ein Ablauf, der Programmanweisungen ausführt. Mehrere Threads können dieselben Daten benutzen. Viele Änderungen bestehen aus mehreren Einzelschritten. Zwischen diesen Schritten ist der Zustand der Daten nicht stimmig. Man nennt ihn inkonsistent.
Ein Beispiel ist das Erhöhen eines Zählers um 1. Es wird hier angenommen, dass der Zugriff auf den Zähler nur mit einer Lese- oder einer Schreiboperation möglich ist, nicht mit einer von Haus aus unteilbaren Erhöhung. Dann sind es drei Schritte: den Zähler in eine lokale Variable lesen, die lokale Variable um 1 erhöhen, die lokale Variable in den Zähler schreiben.
Kritische Abschnitte bestehen aus mehreren Einzelanweisungen. Ihre Zwischenergebnisse sind inkonsistente Zustände, auf die andere Threads keinen Zugriff erhalten dürfen. Kritisch ist so ein Abschnitt, wenn er auf ein gemeinsam genutztes Betriebsmittel zugreift, das nur exklusiv benutzt werden darf, zum Beispiel eine gemeinsame Variable, die er verändert. Das Ergebnis darf nur als eine unteilbare Einheit nach außen sichtbar werden. Eine Operation, die als Ganzes abläuft und von anderen nicht unterbrochen werden kann, heißt atomare Operation.
Definition
Atomare Operation
Eine atomare Operation ist ein Verbund von Einzeloperationen, der als logische Einheit gilt. Er läuft nur als Ganzes erfolgreich ab oder schlägt fehl und kann nicht durch andere Operationen unterbrochen werden.
Merke: Ein kritischer Abschnitt besteht aus mehreren Einzelanweisungen auf einem gemeinsam genutzten Betriebsmittel. Ihre Zwischenergebnisse sind inkonsistent und für andere Threads tabu.
Teste dich
Zwei Threads, X und Y, führen denselben Abschnitt aus: Zähler lesen, um 1 erhöhen, Zähler schreiben. Das Betriebssystem verschränkt die beiden Abläufe zeitlich. Die Threads haben darauf standardmäßig keinen Einfluss.
Im Ablauf der Tabelle lesen beide Threads den Zähler, bevor einer von ihnen schreibt. Beide haben dann den gleichen ursprünglichen Wert als private Kopie. Beide erhöhen ihre eigene Kopie um 1. Danach schreibt erst X und dann Y. Y überschreibt also, was X gespeichert hat. Der Zähler hat am Ende das falsche Ergebnis 1. Wir nehmen hier den Anfangswert 0 an. Dann hätte der Zähler nach zwei Erhöhungen den Wert 2.
Solch eine Situation heißt Race Condition (Wettlaufsituation). Das Ergebnis hängt davon ab, wie die Einzelschritte zeitlich liegen. Solche Fehler sind oft schwer zu finden. Fehler, die nur bei mehreren gleichzeitig laufenden Threads auftreten, zeigen sich möglicherweise unter normalen Betriebsbedingungen überhaupt nicht. Schon zusätzliche Protokollausgaben (Logging) oder ein Debug-Modus, also ein Modus zur Fehlersuche, können die Symptome völlig verschwinden lassen.
Definition
Race Condition
Eine Race Condition (Wettlaufsituation) ist eine Konstellation, in der das Ergebnis einer Operation vom zeitlichen Verhalten bestimmter Einzeloperationen oder der Umgebung abhängt.
| Schritt | Thread X | Thread Y |
|---|---|---|
| 1 | liest den Zähler (0) | |
| 2 | liest den Zähler (0) | |
| 3 | erhöht seine Kopie auf 1 | |
| 4 | erhöht seine Kopie auf 1 | |
| 5 | schreibt 1 in den Zähler | |
| 6 | schreibt 1 in den Zähler |
Teste dich
Manche Betriebsmittel dürfen ihrer Natur nach nur exklusiv benutzt werden. Betriebsmittel sind zum Beispiel Daten im Speicher, Verbindungen oder Geräte. Verändert ein Thread ein solches Betriebsmittel, darf es für andere Threads nicht zugreifbar sein. Ein Programmabschnitt, in dem so auf ein gemeinsam genutztes Betriebsmittel zugegriffen wird, ist ein kritischer Abschnitt.
Läuft die Ausführung solcher Abschnitte zeitlich verschränkt, führt das zu unvorhersagbaren Ergebnissen oder inkonsistenten Zuständen. Das hast du beim Zähler gesehen. Deshalb gilt: Wenn sich ein Thread in einem kritischen Abschnitt für ein Betriebsmittel befindet, darf kein anderer Thread in einen kritischen Abschnitt für dasselbe Betriebsmittel.
Das gilt auch für verschiedene Abschnitte. Beispiel B: Ein zweiter Abschnitt verringert dieselbe Variable zaehler um 1. Aus Konsistenzgründen darf sich dann zu einer Zeit maximal ein Thread in beiden Abschnitten aufhalten.
Es ist nicht erforderlich, eine strenge Reihenfolge festzulegen. Es reicht ein wechselseitiger Ausschluss: Die Abschnitte laufen nacheinander, in beliebiger Reihenfolge.
Beispiel: Mit Sperre: A und B erhöhen den Wert 1
- 1Der gespeicherte Wert ist 1. System A sperrt den Zugriff gegen fremde Zugriffe.
- 2System B versucht auch zu sperren. Das geht nicht, also wartet B.
- 3A liest 1, erhöht auf 2, speichert 2 und hebt die Sperre auf.
- 4Jetzt sperrt B, liest 2, erhöht auf 3, speichert 3 und hebt die Sperre auf.
- 5Ergebnis: 3, wie erwartet.
Merke: Wer ein gemeinsames Betriebsmittel verändert, darf es nicht mit einem anderen Thread zugleich tun. Die Reihenfolge ist egal, das Nacheinander zählt.
Teste dich
Eine Lösung für das Problem des kritischen Abschnitts muss mehrere Anforderungen erfüllen. Der wechselseitige Ausschluss (kurz Mutex, von englisch „mutual exclusion“) ist eine Gruppe von Verfahren, die das Problem lösen. Sie koordinieren den Ablauf so, dass andere Threads vom kritischen Abschnitt ausgeschlossen sind, solange einer darin ist.
Die Anforderungen stehen in der Tabelle. Sind sie erfüllt, kann die Konsistenz der Betriebsmittel gewährleistet werden. Außerdem kommt jeder Thread in endlicher Zeit in seinen kritischen Abschnitt und wird nicht grundsätzlich am Eintritt gehindert.
In Java gibt es dafür eine Standardlösung: den Block synchronized(obj). Jeder andere Thread, der ebenfalls synchronized(obj) aufruft, muss bis zum Ende dieses Abschnitts warten. Bei synchronized-Methoden gilt: Auf die Attribute eines Objekts greift zu jedem Zeitpunkt höchstens ein Thread im Rahmen einer solchen Methode zu.
Achtung: Der gegenseitige Ausschluss birgt die Gefahr von Verklemmungen (Deadlocks). Dabei kann keiner der Prozesse mehr fortfahren, weil jeweils ein Prozess den anderen blockiert.
Definition
Wechselseitiger Ausschluss (Mutex)
Mutex ist eine Gruppe von Verfahren, die das Problem des kritischen Abschnitts löst. Befindet sich schon ein Thread im kritischen Abschnitt, sind die anderen davon ausgeschlossen.
| Anforderung | Bedeutung |
|---|---|
| Wechselseitiger Ausschluss | Höchstens ein Thread ist je Betriebsmittel im kritischen Abschnitt. |
| Fortschritt | Ein Anhalten außerhalb des Abschnitts darf die anderen nicht aufhalten. |
| Begrenzte Wartezeit | Kein Thread wird beliebig lange vom Eintritt ausgeschlossen. |
| Geschwindigkeit | Die Lösung nimmt nichts über die Geschwindigkeit der Threads an. |
Beispiel: Java: synchronized(obj)
- 1synchronized(obj) { int a = obj.w; a = a + 1; obj.w = a; }
- 2Der Block in { } ist der kritische Abschnitt: Er liest w, erhöht um 1 und schreibt zurück.
- 3Das Objekt obj enthält die Variable w und verhindert den gleichzeitigen Zugriff.
- 4Ein anderer Thread mit synchronized(obj) muss warten, bis der Block fertig ist.
Teste dich
Alles auf einen Blick
Kritischer Abschnitt
Kritischer Abschnitt
mehrere Einzelanweisungen auf einem gemeinsam genutzten Betriebsmittel, deren Zwischenergebnisse inkonsistent sind
Race Condition
Das Ergebnis hängt vom zeitlichen Verhalten der Einzeloperationen ab
Zähler
Lesen beide Threads vor dem Schreiben, haben beide denselben alten Wert
Wechselseitiger Ausschluss
Je Betriebsmittel ist höchstens ein Thread im kritischen Abschnitt
Begrenzte Wartezeit
Kein Thread wird beliebig lange vom Eintritt ausgeschlossen
Fortschritt
Ein Anhalten außerhalb des Abschnitts darf die anderen nicht beeinflussen
Musteraufgabe · Schritt für Schritt
Erhöhen und Verringern zugleich (ausgedachtes Beispiel)
- 1Annahme: Der Zähler steht auf 5. Thread P erhöht ihn um 1, Thread Q verringert ihn um 1. Erwartet wird 5.
- 2Ablauf ohne Sperre: P liest 5. Q liest ebenfalls 5, weil P noch nicht geschrieben hat.
- 3P erhöht seine Kopie auf 6. Q verringert seine Kopie auf 4.
- 4P schreibt 6. Danach schreibt Q die 4.
- 5Ergebnis: 4 statt 5. Die Erhöhung von P ging verloren.
- 6Schreibt Q zuerst und P zuletzt, ergibt sich 6. Das Ergebnis hängt also vom zeitlichen Ablauf ab: eine Race Condition.
- 7Mit Ausschluss läuft ein Abschnitt nach dem anderen: P macht aus 5 die 6, danach Q aus 6 die 5. Ergebnis: 5.
Fehler finden
Prüfe die Schritte beim Kartenzähler (ausgedachtes Beispiel) In einer Zeile steckt ein Fehler. Tippe sie an.
Fehler in Zeile 5N hat den Wert von M nie gesehen, sondern nur seine alte private Kopie 10 benutzt. Richtig: Der Endwert ist 11 statt der erwarteten 12.
Lückentext
Wähl in jeder Lücke das passende Wort und prüf dann deine Antworten.
Ein kritischer Abschnitt wie „lesen, erhöhen, schreiben“ besteht aus . Im verschränkten Ablauf lesen beide Threads, bevor einer schreibt, und haben dann als private Kopie. Beim wechselseitigen Ausschluss darf sich je Betriebsmittel höchstens im kritischen Abschnitt befinden.
Karteikasten
Erst selbst überlegen, dann umdrehen.
Übung mit Feedback