Search code examples
c++c++11concurrencylockingproducer-consumer

Why condition_variable is waiting for the lock in producer-consumer? C++


See the following classical producer-consumer code:

int main()
{
    std::queue<int> produced_nums;
    std::mutex m;
    std::condition_variable cond_var;
    bool done = false;
    bool notified = false;

    std::thread producer([&]() {
        for (int i = 0; i < 5; ++i) {
            std::this_thread::sleep_for(std::chrono::seconds(1));
            std::unique_lock<std::mutex> lock(m);
            std::cout << "producing " << i << '\n';
            produced_nums.push(i);
            notified = true;
            cond_var.notify_one();
        }

        done = true;
        cond_var.notify_one();
    });

    std::thread consumer([&]() {
        std::unique_lock<std::mutex> lock(m);
        while (!done) {
            while (!notified) {  // loop to avoid spurious wakeups
                cond_var.wait(lock);
            }
            while (!produced_nums.empty()) {
                std::cout << "consuming " << produced_nums.front() << '\n';
                produced_nums.pop();
            }
            notified = false;
        }
    });

    producer.join();
    consumer.join();
}

I have copied this from cppreference.

Everything is pretty much straightforward to me, except the line in the consumer:

cond_var.wait(lock); 

I do understand the loop that waits cond_var to be notified, but why is it waiting for the lock?


Solution

  • cond_var.wait(lock); does not wait for the lock. That line does 3 things

    1. It unlocks the lock variable
    2. It waits until someone signals the condition.
    3. it locks the lock variable again before it returns,

    It does all this atomically. While the thread is waiting for the condition variable, the mutex is not locked - that way your producer thread can acquire the lock and safely set any variables shared between the consumers/producer.

    It locks the mutex again upon return, so the consumer again can safely access the shared variables.

    If you tried to manage locking/unlocking the mutex yourself, you would end up with race conditions betwen locking/unlocking the mutex and waiting/signalling the condition variable - this is why waiting for a condition variable is tied to a mutex - so it can be done atomically, without race conditions.