Reputation: 2836
Given the following C++11 code fragment:
#include <condition_variable>
#include <mutex>
std::mutex block;
long count;
std::condition_variable cv;
void await()
{
std::unique_lock<std::mutex> lk(block);
if (count > 0)
cv.wait(lk);
}
void countDown()
{
std::lock_guard<std::mutex> lk(block);
if (count > 0)
{
count--;
if (count==0) cv.notify_all();
}
}
If it is not clear what I am trying to accomplish, I am wanting calls to await to pause the calling thread while count is greater than 0, and if it has already been reduced to zero, then it should not pause at all. Other threads may call countDown() which will wake all threads that had previously called await.
The above code seems to work in all cases that I've tried, but I have this nagging doubt about it, because it seems to me like there is a possibility for unexpected behavior if the thread calling await() just happens to get preempted immediately after its condition test has been evaluated and just before the thread is actually suspended by the cv.wait() call, and if the countDown function is getting called at this time, and the count equals 0, then it would issue a notify to the condition variable, IF it were actually already waiting on it... but the thread calling await hasn't hit the cv.wait() call yet, so when the thread calling await resumes, it stops at the cv.wait() call and waits indefinitely.
I actually haven't seen this happen yet in practice, but I would like to harden the code against the eventuality.
Upvotes: 0
Views: 83
Reputation: 219448
It is good that you are thinking about these possibilities. But in this case your code is correct and safe.
If await
gets preempted immediately after its condition test has been evaluated and just before the thread is actually suspended by the cv.wait() call, and if the countDown function is getting called at this time, the latter thread will block while trying to obtain the block
mutex until await
actually calls cv.wait(lk)
.
The call to cv.wait(lk)
implicitly releases the lock on block
, and thus now another thread can obtain the lock on block
in countDown()
. And as long as a thread holds the lock on block
in countDown()
(even after cv.notify_all()
is called), the await
thread can not return from cv.wait()
. The await
thread implicitly blocks on trying to re-lock block
during the return from cv.wait()
.
Update
I did make a rookie mistake while reviewing your code though <blush>
.
cv.wait(lk)
may return spuriously. That is, it may return even though it hasn't been notified. To guard against this you should place your wait
under a while loop, instead of under an if:
void await()
{
std::unique_lock<std::mutex> lk(block);
while (count > 0)
cv.wait(lk);
}
Now if the wait returns spuriously, it re-checks the condition, and if still not satisfied, waits again.
Upvotes: 4