Search code examples
c++11condition-variable

std::condition_variable why does it need a std::mutex


I am not sure if I really understand why std::condition_variable needs a additional std::mutex as a parameter? Should it not be locking by its self?

#include <iostream>
#include <condition_variable>
#include <thread>
#include <chrono>

std::condition_variable cv;
std::mutex cv_m;
int i = 0;
bool done = false;

void waits()
{
    std::unique_lock<std::mutex> lk(cv_m);
    std::cout << "Waiting... \n";
    cv.wait(lk, []{return i == 1;});
    std::cout << "...finished waiting. i == 1\n";
    done = true;
}

void signals()
{
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "Notifying falsely...\n";
    cv.notify_one(); // waiting thread is notified with i == 0.
                     // cv.wait wakes up, checks i, and goes back to waiting

    std::unique_lock<std::mutex> lk(cv_m);
    i = 1;
    while (!done) 
    {
        std::cout << "Notifying true change...\n";
        lk.unlock();
        cv.notify_one(); // waiting thread is notified with i == 1, cv.wait returns
        std::this_thread::sleep_for(std::chrono::seconds(1));
        lk.lock();
    }
}

int main()
{
    std::thread t1(waits), t2(signals);
    t1.join(); 
    t2.join();
}

Secondary, in the example they unlock the mutex first (signals method). Why are they doing this? Shoulden't they first lock and then unlock after notify?


Solution

  • The mutex protects the predicate, that is, the thing that you are waiting for. Since the thing you are waiting for is, necessarily, shared between threads, it must be protected somehow.

    In your example above, i == 1 is the predicate. The mutex protects i.

    It may be helpful to take a step back and think about why we need condition variables. One thread detects some state that prevents it from making forward progress and needs to wait for some other thread to change that state. This detection of state has to take place under a mutex because the state must be shared (otherwise, how could another thread change that state?).

    But the thread can't release the mutex and then wait. What if the state changed after the mutex was released but before the thread managed to wait? So you need an atomic "unlock and wait" operation. That's specifically what condition variables provide.

    With no mutex, what would they unlock?

    The choice of whether to signal the condition variable before or after releasing the lock is a complex one with advantages on both sides. Generally speaking, you will get better performance if you signal while holding the lock.