...
Condition variables must be used inside a while
loop. (see See CON54-CPP. Wrap functions that can spuriously wake up in a loop for more information.) . To guarantee liveness, programs must test the while
loop condition before invoking the condition_variable::wait()
member function. This early test checks whether another thread has already satisfied the condition predicate and has sent a notification. Invoking wait()
after the notification has been sent results in indefinite blocking.
...
The notify_one()
member function unblocks one of the threads that are blocked on the specified condition variable at the time of the call. If multiple threads are waiting on the same condition variable, the scheduler can select any of those threads to be awakened (assuming that all threads have the same priority level).
The notify_all()
member function unblocks all of the threads that are blocked on the specified condition variable at the time of the call. The order in which threads execute following a call to notify_all()
is unspecified. Consequently, an unrelated thread could start executing, discover that its condition predicate is satisfied, and resume execution even though it was supposed to remain dormant.
For these reasons, threads must check the condition predicate after the wait()
function returns. A while
loop is the best choice for checking the condition predicate both before and after invoking wait()
.
...
Time | Thread # |
| Action |
---|---|---|---|
0 | 3 | 0 | Thread 3 executes the first time: the predicate is |
1 | 2 | 0 | Thread 2 executes the first time: predicate the predicate is |
2 | 4 | 0 | Thread 4 executes first the first time: the predicate is |
3 | 0 | 0 | Thread 0 executes the first time: the predicate is |
4 | 1 | 1 | Thread 1 executes the first time: the predicate is |
5 | 3 | 2 | Thread 3 wakes up (scheduler choice): the predicate is |
6 | — | — | Thread exhaustion! No There are no more threads to run, and a conditional variable signal is needed to wake up the others. |
This noncompliant code example violates the liveness property.
...
This compliant solution uses notify_all()
to signal all waiting threads instead of a single random thread. Only the run_step()
thread code from the noncompliant code example is modified, as follows:modified.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <condition_variable> #include <iostream> #include <mutex> #include <thread> std::mutex mutex; std::condition_variable cond; void run_step(size_t myStep) { static size_t currentStep = 0; std::unique_lock<std::mutex> lk(mutex); std::cout << "Thread " << myStep << " has the lock" << std::endl; while (currentStep != myStep) { std::cout << "Thread " << myStep << " is sleeping..." << std::endl; cond.wait(lk); std::cout << "Thread " << myStep << " woke up" << std::endl; } // Do processing ... std::cout << "Thread " << myStep << " is processing..." << std::endl; currentStep++; // Signal ALL waiting tasks. cond.notify_all(); std::cout << "Thread " << myStep << " is exiting..." << std::endl; } // ... main() unchanged ... |
...
Another compliant solution is to use a unique condition variable for each thread (all associated with the same mutex). In this case, notify_one()
wakes up only the thread that is waiting on it. This solution is more efficient than using notify_all()
because only the desired thread is awakened.
Note that the condition The condition predicate of the signaled thread must be true; otherwise, a deadlock will occur.
...