Informatik

Kritischer Abschnitt: Race Conditions verstehen

Kritischer Abschnitt: Race Conditions verstehen
Kritischer Abschnitt: Race Conditions verstehen
Für Quiz, Lückentext, Lernkarten und Fortschritt ist JavaScript nötig. Alle Inhalte und Lösungen bleiben direkt lesbar.

Ein kritischer Abschnitt ist ein Codebereich, den bezüglich derselben gemeinsam genutzten Ressource höchstens ein Thread oder Prozess gleichzeitig ausführen darf. So bleiben mehrteilige Änderungen konsistent und ihre Zwischenzustände für konkurrierende Zugriffe verborgen.

Deine Lernziele

Hake ab, was du schon kannst — und komm am Ende hierher zurück!

Woran erkennst du einen kritischen Abschnitt?

Stell dir vor, mehrere Threads verändern denselben Zähler. Jeder Thread arbeitet für sich korrekt. Trotzdem kann ein falsches Gesamtergebnis entstehen, wenn ihre Anweisungen ungünstig ineinandergreifen.

Definition

Kritischer Abschnitt

Ein kritischer Abschnitt ist eine Folge von Anweisungen, die auf eine gemeinsam genutzte Ressource oder einen gemeinsamen Zustand zugreift und deshalb gegenüber korrespondierenden Zugriffen geschützt werden muss. Beispiele für solche Ressourcen sind Variablen, Datenstrukturen, Speicherbereiche, Verbindungen oder Geräte.

Entscheidend sind drei Fragen:

  1. Greifen mehrere Threads oder Prozesse auf dieselbe Ressource zu?
  2. Verändert mindestens einer von ihnen den gemeinsamen Zustand?
  3. Kann eine Operation in mehrere sichtbare Einzelschritte zerfallen?

Wenn alle drei Punkte zutreffen, kann ein kritischer Abschnitt vorliegen.

Merke

„Kritisch“ bedeutet nicht, dass im gesamten Programm nur ein Thread laufen darf. Ausgeschlossen werden nur konkurrierende kritische Abschnitte, die dieselbe Ressource betreffen.

Ein kritischer Abschnitt muss auch nicht ohne jede Unterbrechung ablaufen. Der Scheduler darf einen Thread pausieren. Währenddessen darf jedoch kein anderer Thread einen korrespondierenden kritischen Abschnitt für dieselbe Ressource betreten.

Teste dich
Frage 1 von 1LeichtWelche Situation kennzeichnet einen kritischen Abschnitt am besten?
Lösung: Mehrere Threads können eine gemeinsame Variable in mehreren Schritten verändern. — Kritisch wird ein Abschnitt durch den konkurrierenden Zugriff auf dieselbe Ressource und die Gefahr eines inkonsistenten Zustands.
Wie entsteht eine Race Condition?

Eine Race Condition oder Wettlaufbedingung liegt vor, wenn das Ergebnis von der zeitlichen Reihenfolge nebenläufiger Ereignisse abhängt.

Der gemeinsame Zähler beginnt bei 0. Zwei Threads X und Y sollen ihn jeweils einmal erhöhen. Eine Erhöhung besteht aus drei Schritten:

  1. gemeinsamen Wert lokal einlesen;
  2. lokalen Wert um 1 erhöhen;
  3. lokalen Wert zurückschreiben.
Beispiel

Bei einer ungünstigen Verschränkung geschieht Folgendes:

SchrittThread XThread YGemeinsamer Zähler
1liest 00
2liest 00
3erhöht lokal auf 10
4erhöht lokal auf 10
5schreibt 11
6schreibt 11

Beide Threads haben eine Erhöhung ausgeführt. Trotzdem lautet der Endwert 1 statt 2, weil Y das gleichlautende Ergebnis von X überschreibt. Das verlorene Ergebnis heißt verlorenes Inkrement.

Der kritische Abschnitt umfasst hier die gesamte Folge aus Lesen, Erhöhen und Zurückschreiben. Würde man nur den letzten Schreibzugriff schützen, könnten weiterhin beide Threads denselben alten Wert lesen.

Teste dich
Frage 1 von 1MittelDer Zähler startet bei 0. X liest 0, Y liest ebenfalls 0, danach erhöhen und schreiben beide ihre lokale Kopie. Welcher Endwert entsteht?
Lösung: 1 — Beide lokalen Kopien werden zu 1. Das zweite Schreiben ersetzt den gemeinsamen Wert erneut durch 1.
Welche Abschnitte müssen gemeinsam geschützt werden?

Nicht die Schreibweise oder Position des Codes entscheidet über den gemeinsamen Schutz, sondern die betroffene Ressource.

Angenommen, ein Abschnitt erhöht zaehler, während ein anderer ihn verringert. Obwohl die Abschnitte verschiedene Aufgaben haben, verändern beide dieselbe Variable. Sie sind deshalb korrespondierende kritische Abschnitte und benötigen denselben wechselseitigen Ausschluss.

Definition

Wechselseitiger Ausschluss

Wechselseitiger Ausschluss bedeutet: Bezüglich einer bestimmten Ressource befindet sich zu einem Zeitpunkt höchstens ein Thread in einem zugehörigen kritischen Abschnitt.

Beispiel mit Java

In Java kann eine synchronisierte Instanzmethode so aussehen: public synchronized void increment() { count++; }

Bei Aufrufen derselben Counter-Instanz führt jeweils nur ein Thread diese synchronisierte Methode aus. Zwei verschiedene Instanzen besitzen dagegen getrennte Monitore und schützen sich nicht gegenseitig.

Beispiel

Zwei Threads verwenden dieselbe Counter-Instanz und rufen jeweils 1000-mal die synchronisierte Methode increment() auf. Wenn count bei 0 beginnt, keine weiteren Änderungen erfolgen und das Programm mit join() auf beide Threads wartet, sind alle 2000 Aufrufe abgeschlossen. Der Endwert ist dann 2000.

Merke

Die Sperre muss zur Ressource passen: Gleiche Ressource bedeutet gemeinsamer Schutz. Verschiedene Ressourcen können unabhängig geschützt werden.

Teste dich
Frage 1 von 1MittelAbschnitt A erhöht einen gemeinsamen Zähler. Abschnitt B verringert denselben Zähler. Was ist nötig?
Lösung: A und B müssen gegenüber einander ausgeschlossen werden. — Beide Abschnitte führen eine mehrteilige Änderung derselben Ressource aus und müssen deshalb denselben Ausschluss beachten.
Was zeichnet eine gute Schutzlösung aus?

Eine Sperre verhindert nicht automatisch jedes Problem. Eine Lösung für kritische Abschnitte soll vier Anforderungen erfüllen:

  • Wechselseitiger Ausschluss: Höchstens ein Thread befindet sich bezüglich einer Ressource im kritischen Abschnitt.
  • Fortschritt: Ein beendeter oder außerhalb seines kritischen Abschnitts stehender Thread darf die übrigen Threads nicht am Fortschritt hindern.
  • Begrenzte Wartezeit: Kein wartender Thread darf beliebig lange ausgeschlossen bleiben.
  • Geschwindigkeitsunabhängigkeit: Die Lösung darf keine bestimmte relative Ausführungsgeschwindigkeit der Threads voraussetzen.

Als Synchronisationsmittel kommen unter anderem synchronized, Locks, Monitore und Semaphore infrage. Sie ordnen den Zutritt zum kritischen Abschnitt. Welche Lösung passt, hängt vom betroffenen gemeinsamen Zustand und vom benötigten Ausschluss ab.

Warum sollte der geschützte Bereich klein bleiben?

Während ein Thread die passende Sperre hält, müssen andere interessierte Threads warten. Ein unnötig großer kritischer Abschnitt kann deshalb die Leistung verschlechtern. Falsch aufeinander abgestimmte Sperren können außerdem zu einem Deadlock führen, bei dem Threads dauerhaft aufeinander warten.

Klein bedeutet aber nicht unvollständig: Beim Zähler müssen Lesen, Ändern und Schreiben gemeinsam geschützt bleiben. Sonst besteht die Race Condition weiterhin.

Teste dich
Frage 1 von 1SchwerEine Entwicklerin schützt beim Erhöhen eines Zählers nur das Zurückschreiben. Wie ist die Lösung zu beurteilen?
Lösung: Sie ist unzureichend, weil mehrere Threads weiterhin denselben alten Wert lesen können. — Die Granularität muss genau die zusammengehörige Lese-Änderungs-Schreib-Folge umfassen: nicht weniger, aber auch keinen unnötigen Code.
Karteikasten
Karteikasten

Überlege zuerst selbst und drehe die Karte anschließend zum Prüfen um.

Alles auf einen Blick
Mindmap
  • Kritischer Abschnitt
    • schützt gemeinsam genutzte Ressourcen
    • umfasst zusammengehörige Mehrschrittoperationen
    • verhindert Race Conditions durch wechselseitigen Ausschluss
    • verbindet korrespondierende Codebereiche
    • benötigt Fortschritt und begrenzte Wartezeit
    • sollte vollständig und möglichst klein sein
Abschluss-Check
Teste dich
Frage 1 von 3LeichtWas macht zwei Codeabschnitte zu korrespondierenden kritischen Abschnitten?
Lösung: Sie greifen kritisch auf dieselbe gemeinsam genutzte Ressource zu. — Auch unterschiedlich geschriebene Abschnitte korrespondieren, wenn ihre Zugriffe auf dieselbe Ressource einander ausschließen müssen.
Frage 2 von 3MittelWarum muss beim Zähler die gesamte Folge aus Lesen, Erhöhen und Schreiben geschützt werden?
Lösung: Weil eine Verschränkung innerhalb dieser Folge zu einem verlorenen Inkrement führen kann. — Die drei Schritte bilden gemeinsam eine Änderung. Erst ihr wechselseitiger Ausschluss macht das Ergebnis unabhängig von der Verschränkung.
Frage 3 von 3SchwerZwei Threads nutzen getrennte Counter-Instanzen mit synchronisierten Instanzmethoden, verändern aber eigentlich denselben externen Zustand. Was ist das Problem?
Lösung: Die getrennten Monitore stellen keinen gemeinsamen wechselseitigen Ausschluss für den externen Zustand her. — Eine Synchronisation schützt nur dann passend, wenn alle korrespondierenden Zugriffe dieselbe Sperre verwenden.

Wenn du einen kritischen Abschnitt beurteilst, gehe in dieser Reihenfolge vor: gemeinsame Ressource bestimmen, mehrteilige Zugriffe markieren, mögliche Verschränkung prüfen und anschließend einen gemeinsamen, vollständig abgegrenzten Schutz wählen.

Passend dazu