2

I'm learning condition_variable and running some examples. I'm curious that why the following code get deadlock if I comment the block. It's a simple consumer and producer example using condition_variable. I think it's a deadlock problem, isn't it?

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

using namespace std;

mutex mtx;
condition_variable cv;

int cargo = 0;

bool shipment_available()
{
    return cargo != 0;
}

void consume(int cnt)
{
    for (int i = 0; i < cnt; i++)
    {
        unique_lock<mutex> lck(mtx);
        cv.wait(lck, shipment_available);
        printf("%d\n", cargo);
        cargo = 0;
    }
}

int main()
{
    thread consumer_thread(consume, 10);

    for (int i = 0; i < 10; i++)
    {
        //while (shipment_available())    // Dead lock without this block
        //{
        //    std::this_thread::yield();
        //}
        unique_lock<mutex> lck(mtx);
        cargo = i + 1;
        cv.notify_one();
    }

    consumer_thread.join();
}

It runs well if I uncomment the block.

Sergey
  • 7,985
  • 4
  • 48
  • 80
Li.Cao
  • 59
  • 5

1 Answers1

5

So walk through this highly likely possibility:

  1. main() starts up the consumer thread.
  2. Before the thread can acquire the mutex, main() locks it, increments cargo, and fires the notification, then releases the mutex by scope-rotation ten times.
  3. main() now runs down to join the consumer thread.
  4. Consumer finally acquires the mutex, and prepares to wait on the condition variable for the given predicate (non-zero cargo).
  5. The predicate is already true, so no wait is performed.
  6. The consumer zeros the cargo, then releases the mutex by scope-rotation.
  7. The consumer loops for the second time, locks the mutex, then checks the predicate, only now the predicate is false because cargo is indeed zero, so it waits on the condition variable (releasing the mutex in the process) for a signal that will never come. The only thing that ever sent that signal is main(), and it is just camped out waiting for the consumer thread to join up, which can now never happen.

In short, you seem to be of the belief that condition variables signals stack up. That simply isn't the case. if no one is actively waiting on a notification at the time it is posted, it is simply lost to the ether.

Finally, and this is important, your "fix" is completely wrong in itself. The shipment_available function examines predicate data (i.e. data that must be protected by the mutex enshrouding it, not only for modification, but likewise for examination). Checking that without the mutex locked in main is a recipe for a race condition.

My suggestion is to reduce cargo by one rather than zeroing it out. Alternatively you could increment i by the value of cargo before zeroing it, thereby hastening i's ascent to the cnt limit, but you appear to be using an ascending cargo amount, so you'll have to make other adjustments accordingly.

Andreas
  • 5,393
  • 9
  • 44
  • 53
WhozCraig
  • 65,258
  • 11
  • 75
  • 141
  • You can't call `wait` until you confirm that there is something to wait for. That's what the mutex is for. – David Schwartz Aug 02 '17 at 07:50
  • @DavidSchwartz I'm not sure I understand your comment, David. Was there something in the potential workflow above that was missing? If so, by all means jack the answer and add it in. – WhozCraig Aug 02 '17 at 07:55
  • I'm trying to crystallize the concept you're trying to get across. – David Schwartz Aug 02 '17 at 08:03
  • @DavidSchwartz Ah... ok. Thanks. – WhozCraig Aug 02 '17 at 08:06
  • Thank you very much. I think that's exactly the case because the program finally output "10" only and then get stuck. This is just some example code from some editorial. Thanks again! – Li.Cao Aug 02 '17 at 08:36