Consider the following quote about std::condition_variable from cppreference:
The
condition_variableclass is a synchronization primitive that can be used to block a thread, or multiple threads at the same time, until another thread both modifies a shared variable (the condition), and notifies thecondition_variable.The thread that intends to modify the variable has to
- acquire a
std::mutex(typically viastd::lock_guard)- perform the modification while the lock is held
- execute
notify_oneornotify_allon thestd::condition_variable(the lock does not need to be held for notification)Even if the shared variable is atomic, it must be modified under the mutex in order to correctly publish the modification to the waiting thread.
An exemplary typical scenario of this approach is as follows:
// shared (e.d., global) variables:
bool proceed = false;
std::mutex m;
std::condition_variable cv;
// thread #1:
{
std::unique_lock<std::mutex> l(m);
while (!proceed) cv.wait(l); // or, cv.wait(l, []{ return proceed; });
}
// thread #2:
{
std::lock_guard<std::mutex> l(m);
proceed = true;
}
cv.notify_one();
However, @Tim in this question came up with (kind-of an academic) alternative:
std::atomic<bool> proceed {false}; // atomic to avoid data race
std::mutex m;
std::condition_variable cv;
// thread #1:
{
std::unique_lock<std::mutex> l(m);
while (!proceed) cv.wait(l);
}
// thread #2:
proceed = true; // not protected with mutex
{ std::lock_guard<std::mutex> l(m); }
cv.notify_one();
It obviously does not meet the requirements of the above cppreference quote since proceed shared variable (atomic in this case) is not modified under the mutex.
The question is if this code is correct. In my opinion, it is, since:
First option is that
!proceedinwhile (!proceed)is evaluated as false. In that case,cv.wait(l);is not invoked at all.Second option is that
!proceedinwhile (!proceed)is evaluated as true. In that case,cv.notify_one()cannot happen until thread #1 enterscv.wait(l);.
Am I missing something or is cppreference wrong in this regard? (For instance, are some reordering issues involved?)
And, what if proceed = true; would be changed to proceed.store(true, std::memory_order_relaxed);?