Why pthread_cond_wait Requires a While Loop in C
In C multithreading with POSIX threads (pthreads), calling
pthread_cond_wait inside an if block instead
of a while loop is a critical concurrency bug. A condition
variable wait operation must always be wrapped in a while
loop to repeatedly test the predicate condition before proceeding. This
pattern ensures that a thread does not execute critical code when the
shared state is not ready, safeguarding the program against spurious
wakeups, stolen signals, and race conditions in multi-threaded
environments.
1. Spurious Wakeups
The POSIX standard explicitly allows for "spurious wakeups." A
waiting thread may unblock and return from
pthread_cond_wait even if no thread explicitly called
pthread_cond_signal or pthread_cond_broadcast.
Spurious wakeups occur due to low-level kernel optimizations, context
switches, or signal deliveries at the operating system level.
If an if statement is used:
// DANGEROUS: Susceptible to spurious wakeups
if (!data_is_ready) {
pthread_cond_wait(&cond, &mutex);
}
// Proceed to consume data (may cause a crash or invalid state)If a spurious wakeup occurs, the thread exits the if
block and proceeds to access shared data even though
data_is_ready remains false. A while loop
forces the thread to re-evaluate data_is_ready immediately
after unblocking, sending the thread back to sleep if the wakeup was
spurious.
2. Stolen Signals (Mesa Monitor Semantics)
Pthreads implement Mesa-style condition variables rather than Hoare-style condition variables. In Mesa semantics, when a thread signals a condition variable, the waiting thread is moved from the condition variable's wait queue to the mutex's lock queue, but it does not run immediately.
Between the moment pthread_cond_signal is executed and
the waiting thread actually re-acquires the mutex and runs, another
thread may intervene:
- Thread A signals that a resource is available.
- Thread B (which was not waiting on the condition variable) acquires the mutex first.
- Thread B consumes the resource and sets the condition back to false.
- Thread B releases the mutex.
- Thread A's signaled thread finally acquires the mutex.
If Thread A evaluated the condition with an if
statement, it would assume the resource is still available, leading to
buffer underflows, null-pointer dereferences, or logical corruption. A
while loop catches this state change and forces the thread
to wait again.
3. Broadcast
Handling (pthread_cond_broadcast)
When multiple threads are waiting on a single condition variable and
a producer calls pthread_cond_broadcast, all waiting
threads wake up and compete for the mutex.
Only one thread will acquire the mutex first and consume the shared
resource. By the time the remaining threads sequentially acquire the
mutex, the resource is already exhausted. A while loop
ensures that every subsequent thread checks the predicate and returns to
a sleeping state if the resource has already been consumed by an earlier
thread.
The Correct Implementation
The canonical pattern for condition variable synchronization in C is:
pthread_mutex_lock(&mutex);
while (!predicate_condition) {
pthread_cond_wait(&cond, &mutex);
}
// Safely perform work on shared data
pthread_mutex_unlock(&mutex);Using while guarantees correctness regardless of how
many threads wake up, how the scheduler prioritizes mutex acquisition,
or whether the operating system triggers a spurious wakeup.