6

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?

Dean Seo
  • 5,486
  • 3
  • 30
  • 49
Eduard Rostomyan
  • 7,050
  • 2
  • 37
  • 76
  • What do you mean by _"waiting for the lock"_? `cond_var.wait(lock);` in fact releases the mutex `m` locked in the constructor of `lock` before waiting. Then, if the consumer thread gets notified, `m` is locked again internally, so the thread can safely use `produced_nums` container. – Daniel Langr May 21 '18 at 08:02
  • so it releases the lock for the producer could continue the loop? – Eduard Rostomyan May 21 '18 at 08:04
  • 1
    See http://en.cppreference.com/w/cpp/thread/condition_variable/wait, I think the description if pretty clear. – Daniel Langr May 21 '18 at 08:07
  • The lock is required set to begin the wait, and is atomically(as far as you're concerned) released at the point of waiting. It is the stock model for pthread cond-vars and mutexes. The posted code is broken btw. `done` can be set without protection,The last iteration sets notified and signals, then releases the mutex. then the consumer picks up the mutex, eventually clears notified, circles around to check done (which may still not be set by the producer, but is about to be, unprotected), then falls back into waiting for a `notified` that will never come. – WhozCraig May 21 '18 at 08:13

1 Answers1

6

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.

nos
  • 223,662
  • 58
  • 417
  • 506