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:

  1. Thread A signals that a resource is available.
  2. Thread B (which was not waiting on the condition variable) acquires the mutex first.
  3. Thread B consumes the resource and sets the condition back to false.
  4. Thread B releases the mutex.
  5. 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.