liveBook · Manning

Condition variable

As explained in C++ Concurrency in Action, Second Edition

A condition variable is a synchronization primitive that lets threads wait for a condition and be notified when it may have changed.

A condition variable lets a thread wait for an event, such as work arriving, instead of repeatedly checking shared data. Another thread changes the data and notifies a waiter. The condition variable works alongside a mutex that protects the data being checked. section 4.1 section 4.1.1

In C++, include <condition_variable>. Prefer std::condition_variable when using std::mutex. std::condition_variable_any accepts other mutex-like types, but its flexibility may carry extra costs. section 4.1.1

Wait for work in a queue

A producer locks a mutex, pushes an item onto a queue, then calls notify_one(). A consumer holds a std::unique_lock and calls wait(lock, predicate), where the predicate tests whether the queue is nonempty. Once the wait returns, it removes an item under the lock, unlocks, and processes the item. Notifying after the producer unlocks avoids waking a consumer only to make it wait for that mutex. section 4.1.1

Listing 4.1. Waiting for data to process with std::condition_variable
std::mutex mut;
std::queue<data_chunk> data_queue;                 #1
std::condition_variable data_cond;
void data_preparation_thread()
{
    while(more_data_to_prepare())
    {
        data_chunk const data=prepare_data();
        {
            std::lock_guard<std::mutex> lk(mut);
            data_queue.push(data);                 #2
        }
        data_cond.notify_one();                    #3
    }
}
void data_processing_thread()
{
    while(true)
    {
        std::unique_lock<std::mutex> lk(mut);      #4
        data_cond.wait(
            lk,[]{return !data_queue.empty();});   #5
        data_chunk data=data_queue.front();
        data_queue.pop();
        lk.unlock();                               #6
        process(data);
        if(is_last_chunk(data))
            break;
    }
}


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":1,\"ch\":51},{\"line\":1,\"ch\":53}],[{\"line\":10,\"ch\":51},{\"line\":10,\"ch\":53}],[{\"line\":12,\"ch\":51},{\"line\":12,\"ch\":53}],[{\"line\":19,\"ch\":51},{\"line\":19,\"ch\":53}],[{\"line\":21,\"ch\":51},{\"line\":21,\"ch\":53}],[{\"line\":24,\"ch\":51},{\"line\":24,\"ch\":53}]]"}
!@%STYLE%@!

The producer–consumer listing shows the queue predicate, notify_one(), and the consumer’s unlock before processing. section 4.1.1

The same pattern can be enclosed in a thread-safe queue: push() adds data and notifies; wait_and_pop() waits for a nonempty queue before removing an item. With several consumers dividing the work, notify_one() wakes one waiting thread to check the condition; it does not specify which one. section 4.1.2

How C++ Concurrency in Action, Second Edition covers it

How deep it goes
Works through mutex-protected queue operations and a thread-safe queue abstraction, without describing implementation internals.
Examples
Uses a producer–consumer data queue and a thread-safe queue with push() and wait_and_pop().
What it assumes
Assumes familiarity with basic C++ threads, mutexes, queues, and lambdas while explaining why event waiting replaces periodic checks.
What you can do after
Build it from scratch by pairing a mutex-protected queue predicate with wait() and notify_one().
How it connects
Connects mutexes, unique locks, predicate waits, notifications, and producer–consumer queues.

Also explained in

Compare how 7 books explain condition variable →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of C++ Concurrency in Action, Second Edition. The section links point to the parts of the book it draws on.