Consider the following quote about std::condition_variable
from cppreference:
The
condition_variable
class 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_one
ornotify_all
on 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
!proceed
inwhile (!proceed)
is evaluated as false. In that case,cv.wait(l);
is not invoked at all.Second option is that
!proceed
inwhile (!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);
?