Thread Synchronization and Mutexes
A condition variable does not hold the data or the state itself. The state is a fact about the program, such as whether data is available. The condition variable is the signalling channel that lets a waiting thread be told when that fact has changed.
When One Thread Must Wait for Another
Many programs split their work across several threads, and those threads often depend on one another. A thread may be unable to continue until something happens somewhere else: a piece of data gets produced, a task finishes, or a particular state is reached. The central question is how a thread can pause and wait for such an event without simply spinning in a loop and checking over and over whether the event has happened yet.
Keep this question in mind as you read: when a thread waits, what exactly is it waiting for, and who makes that thing happen? The answer to the second part is what makes this topic interesting, because the waiting thread never produces the event itself. Another thread does that work, and the waiting thread depends on it.
Waiting for Work Done Elsewhere
Notice the direction of the relationship. The waiting thread does not produce the condition. It depends on some other thread doing its job, and the condition variable is the tool that lets the waiting thread rest until that job has had its effect. The condition is a fact about the program's state, such as whether data is available. The condition variable is the channel through which the waiting thread learns that the fact has become true.
Read the diagram below as a sequence of states rather than a list of lines of code. The consumer starts out running, finds that it cannot continue, and moves into a waiting state. It stays parked until the producer's work causes the condition to occur. Only then does it move to a runnable state and continue.
The waiting thread's progress is tied to an event in another thread. It is not tied to a timer, and it is not tied to repeatedly asking whether the event has happened yet. This is the key difference between waiting on a condition variable and polling for a result.
What a Condition Variable Lets a Thread Do
A condition variable is a mechanism that allows one thread to wait for another thread. A waiting thread waits for a condition, often called an event, that occurs as the result of work done by other threads.
Condition variables are the support for this kind of waiting. The source notes that they live in the standard header named condition_variable. Their job is narrow: they let a thread rest until a condition has been reported as true, so that the thread neither burns processor time in a loop nor proceeds before the work it depends on has been done.
A condition variable does not hold the data or the state itself. The state is a fact about the program, such as whether data is available. The condition variable is the signalling channel that lets a waiting thread be told when that fact has changed.
Producer and Consumer Sharing a Queue
The source offers a classical illustration: two threads communicating by passing messages through a queue. One thread, the producer, places items into the queue. Another thread, the consumer, takes items out. The consumer's need for data is the condition it waits on, and the producer's work of adding items is what brings that condition about.
The queue is a shared object, so both threads touch it. The source notes that avoiding race conditions on that queue is part of the same picture, and that in its version of the example the queue and that protective mechanism are declared globally so that both the producer and the consumer can reach them. The article title names that protective mechanism as the mutex. Its role is to keep the two threads from interfering with the queue. The condition variable's role is different: it handles waiting for the queue to be in a state the consumer needs.
Tracing one item through the queue
The consumer arrives at the queue when it is empty. Trace how the consumer waits, how the producer adds an item, and how the consumer receives it.
- 1Step 1: consumer finds the queue empty
The consumer needs data, but the queue has nothing in it. The condition it needs, that data is available, is not true yet.
- 2Step 2: consumer waits
Instead of looping and re-checking, the consumer waits on the condition variable. It is parked while the producer works.
- 3Step 3: producer adds an item
The producer places an item into the shared queue. This is the work that brings the condition about.
- 4Step 4: producer signals
The producer signals the condition variable, which tells the waiting consumer that the fact it needs has become true.
- 5Step 5: consumer resumes and takes the item
The consumer moves out of its waiting state and removes the item from the queue. Its progress depended entirely on the producer's work.
The consumer proceeded only after the producer had produced an item and signalled. The consumer never had to guess when to check again.
Why Coordination Is Tricky
The source describes condition-variable-based sharing as powerful and efficient, and it enables many elegant forms of cooperation. It also warns that the approach can be rather tricky to get right. The source does not list every pitfall, but the timing of the signal relative to the wait is the most important one to understand, and it is easy to get wrong when reasoning about these mechanisms for the first time.
Consider what happens if the producer signals before the consumer has started waiting. A signal sent when no thread is waiting does not wait around for a later arrival. If the consumer then checks the queue, sees it empty, and only afterwards begins to wait, it can end up waiting for a signal that has already been sent. This is sometimes called a missed or lost wakeup. The sequence below traces one way this can happen.
Common mistake Treating a waiting thread as if it were polling
What it looks likeA consumer repeatedly checks whether the queue has an item inside a tight loop, using processor time while it waits.
Why it failsThe source describes condition variables as the way to pause and wait without simply spinning in a loop and checking over and over.
Do this insteadLet the consumer wait on the condition variable so that it is parked until the producer signals.
Common mistake Assuming a signal will be remembered for a thread that is not yet waiting
What it looks likeThe producer signals after adding an item, but the consumer has not yet started waiting, and the consumer then waits anyway.
Why it failsA signal is sent to a condition at a moment in time. If the consumer begins waiting only afterwards, it can wait for an event that has already happened.
Do this insteadCheck the condition and begin waiting as a protected step, so that the state the consumer tested cannot change before it waits.
Common mistake Thinking the condition variable protects the queue
What it looks likeA programmer assumes the condition variable alone prevents two threads from changing the queue at the same time.
Why it failsThe source separates the two roles: the queue is shared and needs protection against race conditions, while the condition variable lets a thread wait for a state.
Do this insteadUse a protective mechanism, which the article title names as the mutex, for access to the queue, and use the condition variable for waiting on its state.
Practice Tracing the Handoff
A consumer finds the shared queue empty and begins waiting on a condition variable. Later, a producer adds an item and signals. Describe, in order, what the consumer is doing at each stage: before the check, while waiting, and after the signal. Then say which thread is the one whose work makes the consumer's condition true.
Hints
- Ask which thread is active and which thread is suspended at each step.
- The consumer's progress depends on the producer's work, not on a timer.
- The condition is a fact about the queue, and the condition variable is how the consumer learns that the fact has changed.
Explain in your own words why a signal that is sent before the consumer starts waiting can leave the consumer stuck, and name the step that must not be separated from the wait to avoid it.
Hints
- Think about what the consumer checked before it began to wait.
- Consider whether a signal waits around for a thread that arrives later.
Key Takeaways
- A condition variable is a mechanism that allows one thread to wait for another thread, so that the waiting thread rests until a condition occurs.
- The waiting thread depends on work performed by another thread, and it is tied to that event rather than to a timer or to repeated polling.
- A producer and a consumer can coordinate through a shared queue: the consumer waits for data, and the producer's work of adding items brings the condition about.
- Condition-variable-based sharing is powerful and efficient, but it is tricky to get right, especially the timing between checking the condition and beginning to wait.
- The condition variable handles waiting for a state, while the protective mechanism named as the mutex handles safe access to the shared queue.