std::condition_variable why does it need a std::mutex
Asked Answered
L

2

7

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?

Lymanlymann answered 12/5, 2016 at 7:50 Comment(0)
T
13

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.

Toni answered 12/5, 2016 at 8:0 Comment(3)
Does the same explanation hold even if I am using a std::atomic<int>?Kinship
@Kinship There's no advantage to making the integer atomic since you have to wrap it in the mutex anyway (to avoid a race condition if the integer changes after you decide to block on the condition variable).Toni
This is the best explaination, wait_on_condition_vars equals "unlock and wait"Balf
C
5

A good rule of thumb to remember when working with multiple threads is that, when you ask a question, the result may be a lie. That is, the answer may have changed since it was given to you. The only way to reliably ask a question is to make it effectively single-threaded. Enter mutexes.

A condition variable waits for a trigger so that it can check its condition. To check its condition, it needs to ask a question.

If you do not lock before waiting, then it is possible that you ask the question and get the condition, and you are told that the condition is false. This becomes a lie as the trigger occurs and the condition becomes true. But you don't know this, since there's no mutex making this effectively single-threaded.

Instead, you wait on the condition variable for a trigger that will never fire, because it already did. This deadlocks.

Coenurus answered 12/5, 2016 at 7:58 Comment(2)
Ok, thanks for that explanation. But may I ask why this is not directly put into the implementation of the std::condition_variable?Lymanlymann
It is. That's why the condition variable's wait function takes the lock as a parameter!Toni

© 2022 - 2024 — McMap. All rights reserved.