1

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?

2
  • you are using .join() so your code is running synchronously. ie. producer completes, only then does consumer 1 complete. If you use .detach() your code will run as expected (non blocking, asynchronous) (apart from the main thread exiting so add another condition variable or put it to sleep. Commented Dec 24, 2020 at 6:18
  • As a really simple rule of thumb, never sleep while you hold a lock. Commented Dec 24, 2020 at 7:43

2 Answers 2

1

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.

Sign up to request clarification or add additional context in comments.

Comments

1

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.