Skip to main content

Condition Variables and the Wait-Signal Pattern

What This Concept Is

A condition variable is a coordination primitive used together with a mutex to let threads wait for a predicate on shared state to become true.

Its API (pthread form):

  • cond_wait(cv, mutex) -- atomically release mutex, put the caller to sleep on cv, reacquire mutex when woken.
  • cond_signal(cv) -- wake one thread waiting on cv (if any).
  • cond_broadcast(cv) -- wake all waiters.

The atomicity of "release the mutex and sleep" is the whole point. If wait were implemented as unlock; sleep;, the signal could arrive between the two steps and be lost.

The correct usage pattern is:

pthread_mutex_lock(&m);
while (!predicate()) {
pthread_cond_wait(&cv, &m);
}
// predicate is now true AND we hold m
...
pthread_mutex_unlock(&m);

The while (not if) is non-negotiable.

Why It Matters Here

Condition variables are how threads wait for logical conditions rather than for a lock to be free. Every producer-consumer buffer, every thread pool idle-wait, every job queue, every "finished" signal is built on them.

They are also the most commonly-misused concurrency primitive in real code. Almost all of the misuse comes from three mistakes:

  1. Calling wait without holding the mutex.
  2. Using if instead of while.
  3. Signaling without updating shared state under the mutex.

Concrete Example

Producer-consumer bounded buffer with one mutex and two condition variables:

int buf[N];
int count = 0, head = 0, tail = 0;
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t not_full = PTHREAD_COND_INITIALIZER;
pthread_cond_t not_empty = PTHREAD_COND_INITIALIZER;

void put(int x) {
pthread_mutex_lock(&m);
while (count == N) pthread_cond_wait(&not_full, &m);
buf[tail] = x;
tail = (tail + 1) % N;
count++;
pthread_cond_signal(&not_empty);
pthread_mutex_unlock(&m);
}
int get(void) {
pthread_mutex_lock(&m);
while (count == 0) pthread_cond_wait(&not_empty, &m);
int x = buf[head];
head = (head + 1) % N;
count--;
pthread_cond_signal(&not_full);
pthread_mutex_unlock(&m);
return x;
}

This is the reference bounded buffer. Nearly every correct variant is structurally the same.

Common Confusion / Misconception

"if should work because a signal means a thread was woken." No. Pthreads condition variables have Mesa semantics: when a waiter wakes, it re-contends for the mutex and by the time it acquires it, another woken thread may have consumed the resource. The waiter must recheck the predicate. Spurious wakeups (waking with no signal at all) are also allowed by the spec. A while handles both.

"I should always broadcast; it is safer." Broadcast wakes every waiter, which then all re-contend for the mutex. With a large waiter set, this is a thundering-herd disaster. Use signal when only one waiter can make progress; use broadcast when the condition change affects all waiters (for example, shutdown, or a buffer state change where multiple waiters with different predicates share the same cv, which is itself a design smell).

"I can signal without holding the mutex." You can, but then the signaler might race with the waiter's predicate check. Always update shared state, then signal, all under the mutex.

How To Use It

Every correct wait-signal pattern has three paired pieces:

  1. A predicate on shared state (for example, count > 0).
  2. A mutex protecting every access to that state.
  3. A condition variable paired with the mutex, signaled whenever the predicate could become true.

When the code reviewer asks "can this miss a wakeup?", your answer must point at the atomic release-mutex-and-sleep and at the while loop.

Check Yourself

  1. Why must cond_wait atomically release the mutex and sleep?
  2. Why must the predicate be checked in a while rather than an if?
  3. When should you broadcast instead of signal?

Mini Drill or Application

Rewrite the bounded buffer above to use a single condition variable instead of two. Identify the schedule where a producer's signal wakes another producer that still cannot proceed, and explain why while saves you.

Read This Only If Stuck