Hacker News new | ask | show | jobs
by usefulcat 2352 days ago
In this case I think it should be ok because !this->tasks.empty() is part of the predicate, and will be true after a new item is enqueued (and before notify is called). So if the producer is interrupted between releasing the mutex and calling notify, the consumer would not call wait.

But yeah, in general I agree with the advice.. I'm pretty sure I've made that mistake before.

1 comments

It depends on if the predicate can (also) be changed without the lock being held, as otherwise the release/acquire ordering semantics are not guaranteed. So long as items are only every enqueued with the lock held, it should be ok.
according to cppreference:

"Even if the shared variable is atomic, it must be modified under the mutex in order to correctly publish the modification to the waiting thread."

That's not normative but I can't be bothered to check the c++ standard for exact wordings. Also SuSv4 doesn't have an explicit prohibition on modifying the predicate outside a critical section. But if you do, you are truly on your own.

It is because condition variables have no state, i.e. calling signal without waiters is equivalent to doing nothing and does not affect a future wait. The concern is that a thread is switched out after it checks the condition but before it calls wait, and in that moment the condition variable is signaled. The thread then calls wait and is not awoken (potentially never to be awoken). If the predicate is not altered without the mutex and the waiting thread has the mutex at the time it checks the predicate (this latter part is a must in all cases), then it is guaranteed the predicate will remain unmet at least until the thread has switched to a waiting state.

So simply using an atomic (even with a full release-acquire memory barrier) is not necessarily sufficient as that would only enforce the coherence but not prevent the scheduler from interrupting at the wrong moment.