Context:
In every examples I can see about the use of std::condition_variable::wait()
, including those coming from cppreference.com, there is never any synchronization mechanism used to protect the predicate evaluation against data races.
For example:
std::mutex m;
std::condition_variable cv;
int i = 0;
void waiting_func()
{
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, [](){return i > 0;}); // No lock/unlock around the access of the global and shared variable i.
// ...
}
Question:
If there is no such synchronization, even from reputable sources examples, I guess that it is because it is not necessary.
But I'm wondering why ? How does std::condition_variable::wait()
evaluate the predicate so that it is thread-safe ?
My thoughts:
I came up with two possibilities:
std::condition_variable::wait()
function reacquire the mutex before evaluating the predicate.In the case of point 2.), it could be safe if the thread that modifies i
(and call the std::condition_variable::notify_one()
) locks the mutex m
before doing so.
For example:
void modify_func()
{
{
std::scoped_lock<std::mutex> lk(m); // Acquire the mutex
i += 1; // Modify i
} // Release the mutex
cv.notify_one();
}
Of course, another possibility is that my understanding is completely wrong and I missed the point then.
Anyway, I'm really surprised that I could not find any details about it on the documentation.
Your second alternative is correct. As described on cppreference, the predicate overload behaves as
while (!pred()) {
wait(lock);
}
does and wait(lock)
always relocks before returning. See wait
postconditions in [thred.condition.condvar]/12 and predicate overload behavior in [thread.condition.condvar]/15 of the C++17 standard (draft N4659).
And yes, the mutex must be locked while i
is being modified, even if it is atomic. See e.g. this question.