Date: | September 25, 2017 / year-entry #214 |
Tags: | code |
Orig Link: | https://blogs.msdn.microsoft.com/oldnewthing/20170925-00/?p=97075 |
Comments: | 6 |
Summary: | It shouldn't matter. |
Suppose you call
The answer is, "It shouldn't matter." The intended use pattern for a condition variable is to do the following:
And the code that establishes the condition (or at least
changes the condition so it might be true for at least one waiter)
calls
If you follow this pattern, then it doesn't matter whether
a call to
In other words, if you are counting on an unnecessary wake being saved and waking up a future sleep, then that means that you went to sleep before checking the condition. (Because if you had checked the condition, you would have avoided the sleep.) You're holding it wrong. Conversely, if you didn't expect the unnecessary wake to be remembered, but you got one anyway, well, that's also permitted because condition variables are explicitly documented as subject to spurious wakes. Again, if you follow the intended use pattern, spurious wakes aren't a problem (aside from performance) because the recommended pattern is to re-check the condition after the sleep wakes. If the wake were spurious, the check would fail, and you would go back to sleep. In summary, if you wake a condition variable when nobody is waiting for it, it is unspecified whether the wake is saved for the next thread that waits, and that's okay, because if you follow the intended use pattern, it doesn't matter. |
Comments (6)
Comments are closed. |
> spurious wakes aren’t a problem (aside from performance)
I’m not sure why you dismiss performance so casually… If the CV wakes a thread (spurious or not), the thread by definition is holding a mutex which means other threads may not be able to make progress (on top of a potentially needless context switch). That by itself might be a genuine problem if many of the wakeups are spurious.
You shouldn’t say it absolutely doesn’t matter — you can only say it _usually_ doesn’t matter.
As an aside, C++11’s std::condition_variable specifies a total “happens before” ordering on CV wakeups and wait()s to address this issue.
I’m saying that spurious wakeups aren’t a problem from a correctness point of view. They impact performance but not correctness.
…. except, unless I’m missing something, it doesn’t actually address these issues so much as abstract the loop away. Even then, spurious wakeups can happen for unrelated reasons, so handling the condition is essentially some form of standard “tax”
I’ve just had a look through the last C++11 draft (n3337), and I can’t find the language you’re referring to. 30.5 requires an unspecified total order for the parts of the CV operation, and 30.5.1 says that spurious wake-ups are allowed.
There’s a variant on wait that takes a predicate, and is documented to be equivalent to while(!predicate) wait();, but possibly using library optimizations to reduce spurious wake-ups. However, it’s legitimate to have spurious wake-ups and tests of the predicate.
> I’ve just had a look through the last C++11 draft (n3337), and I can’t find the language you’re referring to. 30.5 requires an unspecified total order for the parts of the CV operation, and 30.5.1 says that spurious wake-ups are allowed.
In the C++17 draft (n4660), section 33.5:
The implementation shall behave as if all executions of notify_one, notify_all, and each part of the wait,
wait_for, and wait_until executions are executed in a single unspecified total order consistent with the
“happens before” order.
That combined with the definition of notify_all() (Unblocks all threads that are blocked waiting for *this), unambiguously addresses the “is the wake saved for the next thread that waits” question in the first paragraph.
The spurious wake is hardly ever a problem for performance although of course you need to profile to be sure.
It is usually caused by a broadcast wake sent to multiple waiters. By the time the “spurious” wake happens, the other threads have consumed all available work events. And so the wasted wake mutexes are burned on threads doing no work but don’t interrupt the other threads, because they’re busy working.