Bedingungsvariable (Condition Variable) ist eine Synchronisationsprimitive, die einen Thread blockiert, bis eine bestimmte Bedingung erfüllt ist. Sie entkoppelt das Warten von der Bedingungsprüfung: Der Thread schläft, bis ihn ein anderer Thread weckt — er verbrennt dabei keine Rechenzeit. Eine Bedingungsvariable wird immer zusammen mit einem Mutex verwendet.
Das Grundmuster: wait, signal, broadcast
Eine Bedingungsvariable kennt drei Operationen:
wait(): Der Thread gibt den zugehörigen Mutex frei und blockiert.signal()(auchnotify()): Weckt genau einen wartenden Thread.broadcast()(auchnotifyAll()): Weckt alle wartenden Threads.
Der Mutex ist entscheidend, weil die Bedingungsprüfung und das Warten eine atomare Einheit bilden müssen: Der Thread prüft die Bedingung unter dem Mutex; ist sie nicht erfüllt, gibt wait() den Mutex atomar frei und legt den Thread schlafen. So kann zwischen Prüfung und Schlafen kein anderer Thread die Bedingung ändern, ohne dass der Wartende es mitbekommt.
Warum die Bedingung immer in einer while-Schleife geprüft wird
Ein Wartender darf sich nach dem Aufwachen nicht darauf verlassen, dass die Bedingung noch gilt:
pthread_mutex_lock(&m);
while (puffer_voll) {
pthread_cond_wait(&cv, &m);
}
// Hier ist garantiert Platz im Puffer
pthread_mutex_unlock(&m);
Der Grund sind Spurious Wakeups: POSIX erlaubt, dass wait() auch ohne Signal zurückkehrt. Zusätzlich gilt bei den üblichen Implementierungen die Mesa-Semantik (nach dem Xerox-PARC-Betriebssystem Mesa, Lampson/Redell 1980): signal() macht den Wartenden nur lauffähig, übergibt ihm aber nicht sofort den Mutex — ein anderer Thread kann dazwischen die Bedingung erneut ändern. Die while-Schleife macht das Programm gegen beide Fälle robust.
Ursprung und Semantik
Bedingungsvariablen stammen aus der Monitor-Forschung: Per Brinch Hansen und C. A. R. Hoare entwickelten das Monitor-Konzept Anfang der 1970er-Jahre; Brinch Hansen implementierte es erstmals in der Sprache Concurrent Pascal. Hoares Original-Semantik (signal-and-wait) übergibt die Kontrolle sofort an den geweckten Thread; die verbreitete Mesa-Semantik (signal-and-continue) nicht. Praktisch alle modernen Bibliotheken — POSIX, C++, Java, C#, Python — arbeiten nach Mesa-Semantik, weshalb die while-Schleife Standard ist.
Bedingungsvariablen in den gängigen Sprachen
- C/POSIX:
pthread_cond_tmitpthread_cond_wait,pthread_cond_signal,pthread_cond_broadcast - C++:
std::condition_variable(seit C++11, mitstd::unique_lock) - Java: jedes Objekt als Monitor mit
Object.wait()/notify()/notifyAll(); zusätzlichjava.util.concurrent.locks.ConditionfürReentrantLock - C#:
Monitor.Wait(),Monitor.Pulse(),Monitor.PulseAll() - Python:
threading.Condition - Go:
sync.Cond(meist über Channels als idiomatische Alternative umgangen)
Typische Fallstricke
- Lost Wakeup: Ein Signal, das gesendet wird, bevor der Empfänger in
wait()ist, geht verloren — die Bedingung muss deshalb immer unter dem Mutex geprüft werden. - Vergessenes Signal: Ändert ein Producer den Zustand, ohne
signal()/broadcast()aufzurufen, schläft der Consumer weiter. - if statt while: Bricht der Thread nach dem Aufwachen sofort ab, verarbeitet er unter Umständen einen ungültigen Zustand (Spurious Wakeup, Mesa-Semantik).
Abgrenzung: Semaphor, Monitor, Busy Waiting
Ein Semaphor ist ein Zähler und hat kein „Gedächtnis" für Signale im Warteschlangen-Sinn — eine Bedingungsvariable kann komplexe, zusammengesetzte Bedingungen ausdrücken, die ein Semaphor nur umständlich abbildet. Das Monitor-Konstrukt kapselt Daten, Mutex und Bedingungsvariablen zu einer Einheit; die Bedingungsvariable ist die Primitive darunter. Und statt aktiv zu warten wie beim Busy Waiting, blockiert die Bedingungsvariable den Thread — der Futex setzt genau das unter Linux effizient um. Zusammen mit einem Mutex und zwei Bedingungsvariablen entsteht das klassische Producer-Consumer-Muster.
Verwandte Grundlagen: Monitor (Synchronisation), Semaphor, Mutex, Producer-Consumer, Busy Waiting, Futex, Thread, Nebenläufigkeit.