While mutexes serialize access to shared data, they offer no facility to wait for a particular condition to occur. A thread awaiting the condition that a queue is non empty or a flag has been set could lock a mutex, inspect the state of the data, unlock the mutex, and try again.
This method is called busy waiting, or spinning. The processor cycles dedicated to the spinning thread are effectively wasted, and contention on the mutex is increased.
A condition variable is the solution. By allowing the thread in need of that condition to sleep until some other thread announces that the state of the shared data may have changed, it frees the processor to do useful work.
It releases the mutex it holds while waiting. When it is woken, it reacquires the mutex before continuing, so the mutual exclusion guarantees of the mutex are preserved.
The standard library facility for this is std::condition_variable, found in the header <condition_variable>. It is used with a std::unique_lock<std::mutex>, or another compatible lockable type.
A plain std::lock_guard cannot be used because wait must temporarily unlock the mutex and then lock it again.
A thread that wishes to wait for a condition should perform the following while holding the mutex:
Check whether the condition you are waiting for has already been satisfied.
If not, call
waiton the condition variable, passing thestd::unique_lock.waitatomically releases the mutex held by thestd::unique_lockand blocks the thread.When another thread calls
notify_oneornotify_all, the blocked thread is woken, reacquires the mutex, and returns fromwait.Because waking up may have been spurious, or because another thread may have mutated the state again before the mutex was reacquired, the condition must be checked again. This is why a loop is required.
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <chrono>
std::mutex mtx;
std::condition_variable cv;
std::queue<int> data_queue;
bool finished = false;
void producer()
{
for (int i = 0; i < 5; ++i) {
{
// Declare lock before code needs to call it
std::lock_guard<std::mutex> lock(mtx);
data_queue.push(i);
std::cout << "Produced " << i << '\n';
}
cv.notify_one();
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
{
std::lock_guard<std::mutex> lock(mtx);
finished = true;
}
cv.notify_one();
}
void consumer()
{
while (true) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] {
return !data_queue.empty() || finished;
});
if (data_queue.empty() && finished)
break;
int value = data_queue.front();
data_queue.pop();
lock.unlock();
std::cout << "Consumed " << value << '\n';
}
}
int main()
{
std::thread prod(producer);
std::thread cons(consumer);
prod.join();
cons.join();
return 0;
}
In the example above, the producer thread repeatedly places an integer into the shared queue and calls notify_one. The consumer thread waits until the queue is non empty or the finished flag has been set.
The wait function receives a predicate, which is a callable that returns true when the condition being waited for has occurred.
If the predicate is false, the thread releases the mutex and sleeps until it is woken. At that point it reacquires the mutex and checks the predicate again.
When the predicate returns true, wait returns to the caller. This protects against spurious wakeups and against another consumer removing the item between the notification and the acquisition of the mutex.
It is legal to call wait without passing a predicate, but doing so is easier to get wrong:
// Repeat the block while this condition remains true
while (data_queue.empty() && !finished)
cv.wait(lock);
The check and the call to wait occur while the thread holds the mutex, so the check itself is safe. After waking, however, the condition must be checked again because the thread may have been woken spuriously or another thread may already have consumed the available data.
Forgetting to place the call inside a loop is a common source of errors. The predicate form performs that loop internally, so it is usually the simplest and safest interface to use.
notify_onewakes a single thread, if any, waiting on the condition variable. This is useful when only one waiting thread can benefit from the change, such as in a single consumer case.notify_allwakes every thread waiting on the condition variable. This is necessary when more than one thread may be able to proceed, or when different threads are waiting for different conditions using the same condition variable.
It is valid to call notify_one or notify_all without holding the mutex. On some platforms, calling notify while still holding the mutex may increase the chance that the awakened thread immediately blocks again while trying to acquire it.
The notification itself does not have to occur inside the critical section. The shared state that satisfies the predicate, however, must be modified while the mutex is held.
A lost wakeup occurs when notify_one or notify_all is called before another thread has begun waiting. Ordinary condition variables do not store notifications, so a notification sent while no thread is waiting disappears.
The standard solution is to store the condition in shared state, protect that state with the mutex, and use the predicate form of wait.
The condition variable itself is not the memory of the event. The shared state is the memory. If the producer changes the state before the consumer begins waiting, the consumer checks the predicate, sees that the condition is already true, and does not sleep.
Because checking the predicate and entering the wait are coordinated through the same mutex, another thread cannot modify the protected condition in the vulnerable interval between the check and the wait.
When a thread should not wait indefinitely, wait_for or wait_until can be used. Both functions also support predicates.
std::unique_lock<std::mutex> lock(mtx);
if (cv.wait_for(
lock,
std::chrono::seconds(1),
[] { return !data_queue.empty(); })) {
// data available
} else {
// timeout expired
}
The predicate version returns true if the condition became true before the timeout and false if the timeout expired first.
Without a predicate, the caller must examine the returned status and check the condition again because the thread may have been woken spuriously.
Ensure that the shared data on which the condition depends is protected by the same mutex used for waiting.
Prefer the predicate versions of
wait,wait_for, andwait_until.Keep the block that modifies the shared data as short as possible.
A condition variable does not itself protect data. Forgetting to use the mutex still results in data races.
std::condition_variableis used withstd::unique_lock<std::mutex>. For other lock types, the more generalstd::condition_variable_anyis available.
The producer consumer pattern shown above is only one example. The same mechanism is used in thread safe queues and other synchronization facilities that coordinate work between multiple threads.
0 comments
No comments yet. Be the first.