Introduction
Condition variables solve a specific waiting problem in synchronization. A thread may hold the mutex for shared data, but still be unable to continue because the required state is not ready.
For example, a consumer may lock a queue and find it empty. Instead of repeatedly checking and wasting CPU, it should sleep until the queue may contain an item.
Why Condition Variables Exist
A mutex protects shared state, but it does not tell a thread when the state becomes useful. It only controls safe access to that state.
A condition variable gives threads a waiting place. It lets a thread sleep while waiting for a shared-state condition, such as “queue is not empty” or “buffer is not full.”
Condition variables do not replace mutual exclusion. They work together with a mutex.
Predicate, Mutex, and Condition Variable
The condition being waited for is often called a predicate. A predicate is a true-or-false statement about shared state.
Part | Role |
|---|---|
Predicate | The shared-state condition, such as |
Mutex | Protects checking and changing the shared state |
Condition variable | Lets threads sleep and wake around that condition |
The condition variable is not the condition itself. It is the waiting mechanism for threads that cannot proceed until the predicate may be true.
Common Operations
Condition variables usually have three main operations.
Operation | Meaning |
|---|---|
| Put the thread to sleep until the condition may be true |
| Wake one waiting thread |
| Wake all waiting threads |
The phrase “may be true” matters. A wakeup does not guarantee the predicate is still true when the awakened thread actually runs.
Why Wait Needs a Mutex
A thread must check the shared predicate while holding the mutex. If the predicate is false, it calls wait(cv, mutex).
The basic wait flow is:
Lock mutex: The thread safely checks shared state.
Check predicate: If the condition is false, it cannot continue.
Call wait:
wait(cv, mutex)releases the mutex and sleeps atomically.Wake later: The thread wakes when signaled or broadcast.
Reacquire mutex: Before returning from wait, it gets the mutex again.
Check again: The thread rechecks the predicate before proceeding.
The atomic release-and-sleep step prevents the dangerous gap where a signal could happen after the mutex is released but before the thread actually sleeps.
Why Wait Needs a Mutex
The While-Loop Rule
A waiting thread should use a while loop, not a single if.
lock(mutex)
while condition_is_false:
wait(cv, mutex)
use shared state
unlock(mutex)The loop is necessary because waking up does not prove the condition is still true. Another thread may consume the resource first, or the thread may wake spuriously.
The safe mental sequence is: check condition, wait, wake, recheck condition, then proceed.
Producer-Consumer Example
In the producer-consumer problem, condition variables are commonly used with a bounded buffer.
mutex: Protects the shared buffer statenot_full: Producers wait here when the buffer is fullnot_empty: Consumers wait here when the buffer is empty
A producer locks the mutex and checks whether the buffer is full. If it is full, the producer waits on not_full. When it wakes, it rechecks the condition and adds an item only if space exists.
A consumer locks the mutex and checks whether the buffer is empty. If it is empty, the consumer waits on not_empty. When it wakes, it rechecks the condition and removes an item only if an item exists.
Producer-Consumer Example
Signal vs Broadcast
signal wakes one waiting thread. It is useful when one state change can help one waiter, such as adding one item to a queue.
broadcast wakes all waiting threads. It is useful when many waiters may need to recheck the state, or when the program cannot easily know which waiter should wake.
Broadcast can cause extra wakeups, but that is safe if every waiter uses the while-loop rule and rechecks the predicate.
Missed Wakeup Problem
A missed wakeup happens when a signal occurs before the thread is actually waiting. The signal wakes no one, and the thread may later sleep forever.
Correct condition-variable use avoids this pattern:
Check state with the mutex held.
Use
waitso releasing the mutex and sleeping happen atomically.Change shared state while holding the same mutex.
Signal or broadcast after updating the state.
This pairing is why condition variables are tied to mutex-protected shared state.
Missed Wakeup Problem
Summary
A condition variable lets threads wait until a shared-state condition may become true. The mutex protects the predicate, while the condition variable coordinates sleeping and waking.
The most important rule is to wait in a while loop. A signal or broadcast only means the condition may be true, so every awakened thread must reacquire the mutex and recheck the predicate.
Be the first to add a comment.