c++multithreadinglocking

What happens when a conditional variable gets notified but doesn't have the lock yet?


std::condition_variable cv;
std::mutex m_cnt;
int cnt = 0;
void producer() {
    std::unique_lock<std::mutex> ul(m_cnt);
    cnt++;
    cv.notify_one();
    std::this_thread::sleep_for(std::chrono::seconds(1));
}
void consumer() {
    std::unique_lock<std::mutex> ul(m_cnt);
    cv.wait(ul, [] {return cnt > 0;}); // ---> Here
    cnt--;
}

In the following example, what happens in cv.wait()? It has been notified by producer, but doesn't have the m_cnt(lock) to move further. Once the consumer is "notified" does it keep on trying to acquire the lock indefinitely?


Solution

  • I'm going to answer the question in the title first:

    Question:

    What happens when a conditional variable gets notified but doesn't have the lock yet?

    Answer: The notification is missed. If a (un-timed) wait is initiated after the only notification, it might wait forever. It also might not because spurious wakes are allowed (waking for no reason).

    However: The code you posted does not have this problem.

    Details:

    Let's say (as you imply) that producer() runs first.

    1. It locks the mutex.
    2. It increments cnt.
    3. It notifies cv, with no one waiting on it, so that notification is missed.
    4. It sleeps for 1s.
    5. It releases the mutex.

    And let's say that consumer() starts running any time after step 1 in producer():

    1. It blocks on the mutex until producer() releases it.
    2. It locks the mutex.
    3. It calls cv.wait(ul, [] {return cnt > 0;}).

    At this point it is instructive to look at the spec for this function.

    Effects: Equivalent to:

    while (!pred())
        wait(lock);
    

    Note that !pred() is called before a wait actually happens! The predicate will find that cnt has already been incremented by producer() and thus skip the wait.

    So the missed notification is immaterial.

    1. Decrement cnt.
    2. Release the mutex.

    If consumer() runs first, then it will enter the wait and producer() will have either not started yet, or it will be blocked on the mutex until consumer() enters the wait. Then producer() will increment cnt (step 2) and continue through step 5. consumer() will receive the notification and wait until producer()'s step 5 is done before proceeding with consumer()'s step 4.