Mutex einfach erklärt: Gemeinsame Daten schützen
Ein Mutex sorgt dafür, dass höchstens ein Thread oder Prozess gleichzeitig einen geschützten Codeabschnitt ausführt. So können mehrere nebenläufige Abläufe dieselben Daten verwenden, ohne ihre Änderungen unkontrolliert zu vermischen.
Du lernst, eine Race Condition zu erkennen, einen kritischen Abschnitt passend zu schützen und typische Mutex-Fehler zu vermeiden.
Hake ab, was du schon kannst — und komm am Ende hierher zurück!
Warum gemeinsame Daten Schutz brauchen
Stell dir vor, zwei Threads kopieren fast gleichzeitig jeweils 100 Bytes in denselben globalen Speicherbereich. Das Kopieren besteht aus mehreren Einzelschritten und kann deshalb unterbrochen werden:
- Thread A beginnt mit seiner Kopie.
- Der Scheduler unterbricht A mitten im Kopiervorgang.
- Thread B schreibt seine Daten in denselben Speicherbereich.
- Danach setzt A seinen begonnenen Vorgang fort.
Das Ergebnis kann Teile aus beiden Datenpaketen enthalten. Die Reihenfolge der Threads hat also den Endzustand beeinflusst.
Race Condition
Eine Race Condition ist ein Fehler, bei dem das Ergebnis eines nebenläufigen Programms davon abhängt, in welcher zeitlichen Reihenfolge sich mehrere Zugriffe überschneiden.
Nicht jeder gleichzeitige Zugriff ist problematisch. Zwei Threads dürfen beispielsweise häufig dieselben unveränderten Daten lesen. Schutz wird nötig, wenn mindestens ein Zugriff Daten verändert und mehrere zusammengehörige Schritte nicht unkoordiniert verschränkt werden dürfen.
So schützt ein Mutex den kritischen Abschnitt
Kritischer Abschnitt
Ein kritischer Abschnitt ist der Teil eines Programms, in dem gemeinsam genutzte Daten so bearbeitet werden, dass kein konkurrierender Zugriff dazwischenkommen darf.
Mutex
Ein Mutex ist ein Synchronisationsmechanismus für wechselseitigen Ausschluss. Wer ihn erfolgreich sperrt, erhält exklusiven Zugang zum zugehörigen kritischen Abschnitt. Andere beteiligte Threads oder Prozesse müssen warten oder den Zugriff später erneut versuchen.
Das Grundmuster besteht aus drei Schritten:
- Den Mutex unmittelbar vor dem kritischen Abschnitt sperren.
- Die zusammengehörige Operation vollständig ausführen.
- Den Mutex zuverlässig wieder freigeben.
Beim Kopierbeispiel umfasst der kritische Abschnitt den gesamten Kopiervorgang. Würde der Mutex schon nach dem ersten Teil freigegeben, könnte weiterhin eine Mischung entstehen.
Thread A sperrt den Mutex und kopiert sein vollständiges Datenpaket. Währenddessen erreicht Thread B dieselbe Stelle, kann den belegten Mutex aber nicht erwerben und wartet. Erst nachdem A den Mutex freigegeben hat, darf B sein vollständiges Paket kopieren. Zu jedem Zeitpunkt verändert somit höchstens einer der beiden Threads den Speicherbereich.
Alle Zugriffsstellen auf die geschützte Ressource müssen denselben Mutex beachten. Ein einzelner ungeschützter Schreibzugriff kann die Schutzwirkung umgehen.
Schütze nicht bloß eine einzelne Anweisung, sondern genau die zusammengehörige Operation, die gegenüber konkurrierenden Zugriffen ungeteilt bleiben muss.
Wähle in jeder Lücke die passende Form und prüfe anschließend deine Antworten.
Vor dem kritischen Abschnitt wird der Mutex . Während der Bearbeitung besitzt ein Thread . Nach der Bearbeitung muss er den Mutex . Andere Zugriffsstellen müssen verwenden.
Mutex, Semaphor und Synchronisation unterscheiden
Ein Mutex koordiniert Threads oder Prozesse für einen begrenzten Zugriff auf eine gemeinsame Ressource. Damit ist er ein Mittel der Synchronisation. Er legt aber nicht automatisch den gesamten Ablauf des Programms fest.
Bei typischen Mutex-Implementierungen gehört die Sperre ihrem Besitzer: Nur der Thread oder Prozess, der den Mutex erworben hat, darf ihn wieder freigeben.
Ein Semaphor verwaltet dagegen einen Zähler für verfügbare Berechtigungen oder Ressourcen. Ein binäres Semaphor hat nur zwei Zählerzustände und kann ähnlich wie eine Sperre wirken. Dennoch bleibt ein wichtiger Unterschied: Seine Freigabe ist grundsätzlich nicht in derselben Weise an den Erwerber gebunden.
| Frage | Mutex | Semaphor |
|---|---|---|
| Wie viele dürfen den geschützten Bereich gleichzeitig betreten? | Höchstens einer | Bis zur festgelegten Anzahl |
| Welcher Zustand wird verwaltet? | Gesperrt oder frei | Zähler verfügbarer Berechtigungen |
| Ist die Freigabe typischerweise an den Besitzer gebunden? | Ja | Nein |
Konkrete Bibliotheken können Begriffe unterschiedlich verwenden. Entscheidend ist deshalb auch die dokumentierte Bedeutung des jeweiligen Sperrobjekts.
Warten, Sperrdauer und Deadlocks beherrschen
Ist ein Mutex belegt, kann ein Thread unterschiedlich reagieren:
- Beim passiven Warten versetzt ihn der Scheduler in einen Wartezustand und weckt ihn später wieder auf.
- Beim aktiven Warten prüft er fortlaufend, ob die Sperre frei geworden ist. Eine solche Polling-Schleife wird bei Locks auch als Spinlock bezeichnet und kann Rechenzeit verbrauchen.
- Bei einem späteren Versuch erledigt der Thread zunächst andere Arbeit.
- Ein veralteter Wert kann verworfen werden, wenn ein neuerer Wert ihn ohnehin ersetzen wird und das Programm dies ausdrücklich erlaubt.
Welche Reaktion geeignet ist, hängt von Laufzeitumgebung, Scheduler und Aufgabe ab. Unkontrolliertes, häufiges Polling ist keine allgemeine Standardlösung.
Deadlock
Ein Deadlock oder eine Verklemmung liegt vor, wenn sich beteiligte Threads dauerhaft gegenseitig blockieren und keiner mehr fortfahren kann.
Ein typischer Deadlock entsteht so:
- Thread A hält Mutex 1 und wartet auf Mutex 2.
- Thread B hält Mutex 2 und wartet auf Mutex 1.
- Keiner kann seine erste Sperre freigeben, weil beide auf die zweite warten.
Halte Sperren deshalb nur so lange wie nötig. Wenn mehrere Sperren gemeinsam benötigt werden, hilft eine einheitliche Erwerbsreihenfolge, gegenseitiges Warten zu vermeiden. Bereichsgebundene Sperrobjekte können außerdem dafür sorgen, dass eine Sperre auch beim vorzeitigen Verlassen eines Codebereichs freigegeben wird.
Bei einer Prioritätsinversion wartet ein hochpriorisierter Thread auf eine Ressource, die ein niedrigpriorisierter Thread hält. Ein Thread mittlerer Priorität kann den Besitzer zusätzlich verdrängen und dadurch den hochpriorisierten Thread indirekt länger blockieren. Geeignete Laufzeit- oder Betriebssystemmechanismen müssen diesen Sonderfall berücksichtigen.
Mutex-Code richtig entwerfen und testen
Eine gute Sperre schützt alle Schritte, die gemeinsam konsistent bleiben müssen. Sie sollte aber nicht mehr Code umfassen als nötig. Diese Ausdehnung heißt Granularität des kritischen Abschnitts.
Ein zu kleiner Bereich lässt möglicherweise einen Teil der Operation ungeschützt. Ein unnötig großer Bereich blockiert andere Threads länger und kann die Leistung verschlechtern oder Verklemmungen begünstigen.
Eine Methode berechnet zuerst ausschließlich mit lokalen Daten und schreibt danach ein Ergebnis in eine gemeinsame Liste. Der Mutex muss den gemeinsamen Listen-Zugriff schützen. Die vorherige lokale Berechnung kann außerhalb des kritischen Abschnitts bleiben, sofern sie keine gemeinsam veränderlichen Daten verwendet.
Nebenläufigkeitsfehler treten oft nur bei seltenen zeitlichen Abläufen auf. Ein einziger erfolgreicher Praxistest beweist deshalb keine Fehlerfreiheit. Sinnvoll ist eine Kombination aus:
- Modultests, die an kritischen Stellen gezielt Threadwechsel provozieren;
- Praxistests unter wechselnden Betriebsbedingungen;
- Codereviews, die alle Zugriffe auf die gemeinsame Ressource darauf prüfen, ob derselbe Mutex konsequent verwendet wird.
Karteikasten
Überlege zuerst selbst und drehe die Karte anschließend zum Prüfen um.
Alles auf einen Blick
- Mutex
- Ziel: gemeinsame Daten konsistent halten
- Gefahr ohne Schutz: Race Condition
- Schutzbereich: kritischer Abschnitt
- Ablauf: sperren, bearbeiten, freigeben
- Besitz: Freigabe durch den Erwerber
- Warten: passiv, aktiv oder späterer Versuch
- Risiko bei Fehlgebrauch: Deadlock
- Prüfung: Threadwechsel, Praxistest und Codereview
Abschluss-Check
Prüfe zum Schluss deinen Denkweg: Kannst du bei einem nebenläufigen Zugriff die gemeinsam genutzte Ressource benennen, den vollständigen kritischen Abschnitt markieren und erklären, warum die gewählte Sperre weder zu klein noch unnötig groß ist? Dann kannst du einen Mutex begründet einsetzen.
Mit Google fortfahren