Search code examples
c++predicatecondition-variable

How does std::condition_variable::wait() evaluate the given predicate?


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:

  1. ) Either, the predicate is guaranteed to be atomically evaluated (I've never read such a thing, hence my question)
  2. ) Or, when the notify signal is sent, the 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.


Solution

  • 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.