Spurious wakeup with atomics and condition_variables
Asked Answered
D

1

7

std::atomic<T> and std::condition_variable both have member wait and notify_one functions. In some applications, programmers may have a choice between using either for synchronization purposes. One goal with these wait functions is that they should coordinate with the operating system to minimize spurious wakeups. That is, the operating system should avoid waking the wait-ing thread until notify_one or notify_all are called.

On my machine, sizeof(std::atomic<T>) is sizeof(T) and sizeof(std::condition_variable) is 72. If you exclude std::atomic<T>'s T member, then std::condition_variable reserves 72 bytes for to serve its synchronization purposes while sizeof(std::atomic<T>) reserves 0 bytes.

My question: should I expect different behavior between std::condition_variable's and std::atomic<T>'s wait functions? For example, should std::condition_variable have fewer spurious wakeups?

std::atomic<T>::wait()

std::atomic<T>::notify_one()

std::condition_variable::wait()

std::condition_variable::notify_one()

Dacy answered 11/5, 2022 at 3:11 Comment(5)
condition_variable would take mutex with every wake to check wait condition, and release it when going back to wait. Locking/Releasing mutex is a service call to OS (well depends on implementation but usually is). I expect that 'atomic' would not need such guard to check wait condition. So 'atomic' does not need thread switching to acquire mutex at all.Cahoon
If the thread that calls wait doesn't check that the atomic variable changed, what thread does?Dacy
what do you mean by "doesn't check that the atomic variable changed" ? whole point of wait is to check variable changedCahoon
You said: "So 'atomic' does not need thread switching to acquire mutex at all." I understood this as: "You might think that when atomic checks if its T changed, your processor need to switch to the calling thread. However, since 'atomic would not need such guard to check wait condition', switching threads in unnecessary. Another thread can check whether or not the atomic's T changed, and if it has, THEN wake the calling thread." This makes sense to me. I assume then that some OS process is in charge of checking if atomic's T changed (for ALL atomics currently waiting).Dacy
Yeap, bad wording... Let me correct myself then. cv.wait() releases mutex when going to sleep and locking it again when wakes up because of notification. If mutex is locked by other thread, thread holding cv would go to sleep once again until mutex is released by other thread. Once cv acquire mutex after all, it could check wait condition. Atomic does not need mutex and potentially could cause lesser thread context switching.Cahoon
S
2

My question: should I expect different behavior between std::condition_variable's and std::atomic<T>'s wait functions? For example, should std::condition_variable have fewer spurious wakeups?

std::atomic::wait does not have spurious wake ups. The standard guarantees that a changed value was observed, it says in [atomics.types.generic.general]/30:

Effects: Repeatedly performs the following steps, in order:

(30.1) Evaluates load(order) and compares its value representation for equality against that of old.

(30.2) If they compare unequal, returns.

(30.3) Blocks until it is unblocked by an atomic notifying operation or is unblocked spuriously.

So, if the underlying implementation of atomic wait makes spurious wake ups, they are hidden by the C++ standard library implementation.

If your questions is about whether there are more or fewer spurious wakeups in the underlying implementation of atomics or condition variables, then it is implementation specific. Will depend on operating system and library implementation. Most likely answer is: no, because the ultimate implementation, where OS makes kernel call is highly likely the same.

Stricture answered 15/5, 2022 at 17:29 Comment(1)
thanks, yes I guess I skipped over that it repeats.Dissuasive

© 2022 - 2024 — McMap. All rights reserved.