tau
tau

Reputation: 13

std::condition_variable::wait in c++

I am slightly confused in mutex, lock, and wait.

Here is my code:

void producer(std::mutex* m, std::condition_variable* cv,bool* signal) {

    std::this_thread::sleep_for(std::chrono::seconds(1));

    m->lock();
    *signal = true;
    m->unlock();

    std::this_thread::sleep_for(std::chrono::seconds(3));

    cv->notify_one();

    printf("notify one\n");
    std::this_thread::sleep_for(std::chrono::seconds(10000));
}

void consumer(std::mutex*m, std::condition_variable* cv, bool* signal, int index) {

    if(index == 2) std::this_thread::sleep_for(std::chrono::seconds(2));
    std::unique_lock<std::mutex> lk(*m);

    cv->wait(lk, [&] { return (*signal); });

    printf("consumer %d passes this point!\n", index);
    std::this_thread::sleep_for(std::chrono::seconds(10000));
}

int main() {
    
    bool signal = false;
    std::mutex m;
    std::condition_variable cv;
    std::thread th1(producer, &m, &cv, &signal);
    std::thread th2(consumer, &m, &cv, &signal, 1);
    std::thread th3(consumer, &m, &cv, &signal, 2);

    th1.join();
    th2.join();
    th3.join();

    return 0;
}

std::this_thread::sleep_for is added to explain my question. There are producer, consumer1, and consumer 2. I think that this code should work as follows:

  1. consumer1 meets std::unique_lock<std::mutex> lk(*m); so it locks.
  2. consumer1 meets cv->wait. Because initial value of signal is false, consumer1 is blocked and the lock is released.
  3. producer meets m->lock();, *signal = true;, m->unlock();, and sleep_for. Therefore, signal becomes true.
  4. consumer2 meets std::unique_lock<std::mutex> lk(*m); and cv->wait(lk, [&] { return (*signal); });. Becuase signal is true, this thread just passes it. So, printf("consumer %d passes this point!\n", index); is executed.
  5. producer meets cv->notify_one();. consumer 1 is unblocked and check the condition. Because signal is ture, consumer1 can pass this point. Therefore, consumer1 meets printf.

Consequently, my expected result is

consumer 2 passes this point!
notify one
consumer 1 passes this point!

However, the real result is

consumer 2 passes this point!
notify one

It seems to be that consumer1 cannot pass cv->wait(lk, [&] { return (*signal); });, even though notify_one() is called and the condition is satisfied. Is there anything wrong in my understanding?

Upvotes: 1

Views: 164

Answers (2)

jignatius
jignatius

Reputation: 6484

The problem is consumer 2 doesn't release the lock on the mutex before going to sleep:

std::unique_lock<std::mutex> lk(*m);

cv->wait(lk, [&] { return (*signal); });

printf("consumer %d passes this point!\n", index);   // <-- mutex is still locked here
std::this_thread::sleep_for(std::chrono::seconds(10000));

So even though the condition in consumer 1 may be satisfied, it cannot acquire the lock on the mutex because it's already locked by another thread.

An std::unique_lock gives you more fine grained control than its counterpart std::lock_guard including the ability to unlock. What you can do is add an unlock call before the consumer goes to sleep like this:

...
lk.unlock();
std::this_thread::sleep_for(std::chrono::seconds(10000));

Otherwise the lock on the mutex is only released after consumer 2 has completed executing the consumer() function, i.e. after the long sleep.

Upvotes: 1

Suthiro
Suthiro

Reputation: 1290

You are not releasing lock in consumer routine. The code below does what is expected:

#include <thread>
#include <chrono>
#include <condition_variable>
#include <mutex>

void producer(std::mutex* m, std::condition_variable* cv,bool* signal) {

    std::this_thread::sleep_for(std::chrono::seconds(1));
    {
        std::unique_lock<std::mutex> lk(*m);
        *signal = true;
    }
    std::this_thread::sleep_for(std::chrono::seconds(3));

    cv->notify_all();

    printf("notify one\n");
    std::this_thread::sleep_for(std::chrono::seconds(5));
}

void consumer(std::mutex*m, std::condition_variable* cv, bool* signal, int index) {

    if(index == 2) std::this_thread::sleep_for(std::chrono::seconds(2));

    {
        std::unique_lock<std::mutex> lk(*m);
        cv->wait(lk, [&] { return (*signal); });
    }

    printf("consumer %d passes this point!\n", index);
    std::this_thread::sleep_for(std::chrono::seconds(5));
}

int main() {

    bool signal = false;
    std::mutex m;
    std::condition_variable cv;
    std::thread th1(producer, &m, &cv, &signal);
    std::thread th2(consumer, &m, &cv, &signal, 1);
    std::thread th3(consumer, &m, &cv, &signal, 2);

    th1.join();
    th2.join();
    th3.join();

    return 0;
}

Note the enclosing brackets around std::unique_lock<std::mutex> lk(*m); to provide a local scope.

Upvotes: 1

Related Questions