1

I have an "engine" that asynchronously process tasks and for one task I want to wait until that task is processed.

boost::condition_variable cvWorkDone;

DoSomeWork()
{
   PostAsyncJob(DoWorkAsync)   // is a boost::asio::post

   boost::mutex::scoped_lock lock(mtxWorkDoneCv);
   cvWorkDone.wait(lock);
}


DoWorkAsync()
{
   // do some work ...

   cvWorkDone.notify_one();
}

The problem is that the code above has a race condition. What if DoWorkAsync() notifies the boost::condition_variable before DoSomeWork() waits for it ?

I see that boost::condition_variable::wait has a second parameter, a bool that can be used to implement something like this

bool bWait;

DoSomeWork()
{
   bWait = true;
   PostAsyncJob(DoWorkAsync)   // boost::asio::post

   boost::mutex::scoped_lock lock(mtxWorkDoneCv);
   cvWorkDone.wait(lock, bWait);
}


DoWorkAsync()
{
   // do some work ...
   boost::mutex::scoped_lock lock(mtxWorkDoneCv);
   cvWorkDone.notify_one();      
   bWait = false;
}

but the concurrency is still there ... How can I solve this ?

cprogrammer
  • 5,503
  • 3
  • 36
  • 56

1 Answers1

3

Since condition variables don't maintain state about whether they've been signaled or not, you need to maintain the state for whatever might be the reason for the condition variable to be signaled separately (in some cases, such as queues, the reason for a condition variable to be signaled can go away asynchronously). So you might have something like this in your code:

boost::condition_variable cvWorkDone;
bool workdone = false;

DoSomeWork()
{
   PostAsyncJob(DoWorkAsync)   // is a boost::asio::post

   boost::mutex::scoped_lock lock(mtxWorkDoneCv);
   while (!workdone) {
      cvWorkDone.wait(lock);
   }
}


DoWorkAsync()
{
   // do some work ...

   {   
      boost::mutex::scoped_lock lock(mtxWorkDoneCv);
      workdone = true;
   }
   cvWorkDone.notify_one();
}

Note that this also protects against spurious returns from boost::condition_variable::wait(). From the boost docs on boost::condition_variable::wait():

The thread will unblock when notified by a call to this->notify_one() or this->notify_all(), or spuriously.

Michael Burr
  • 333,147
  • 50
  • 533
  • 760
  • Why is this solving the concurrency issue ? `workdone = true;` and `cvWorkDone.notify_one();` can execute before `!workdone` and `vWorkDone.wait(lock);`. And the main thread will wait for a notify_one that will never come. – cprogrammer Oct 15 '12 at 15:06
  • The thread will unblock when notified by a call to this->notify_one() or this->notify_all(), **or spuriously**. doesn't sound good at all. – cprogrammer Oct 15 '12 at 15:18
  • This solves the concurrency problem because `workdone` is updated and checked only while holding the mutex. The waiter can only get to the `cvWorkDone.wait(lock)` statement if and only if `DoAsyncWork()` has not yet reached the `workdone = true` statement. Conversely, if `DoWorkAsync()` sets `workdone = true` before the waiter reaches the `while (!workdone)`, then the waiter will notice that `workdone` has been set, and won't bother waiting on the condition variable. To reiterate, there's no race there because the threads have to acquire mutex to set or check `workdone`. – Michael Burr Oct 15 '12 at 18:23
  • `boost::condition_variable::wait()` can have spurious wake ups because that's the common behavior for the underlying system mechanism used by boost. For a rationale for why condition variables might behave this way, see http://stackoverflow.com/questions/8594591/why-does-pthread-cond-wait-have-spurious-wakeups So in general, you need to loop on the condition that the condition variable 'signals'. – Michael Burr Oct 15 '12 at 18:27