Starting C 20, std::atomic has wait() and notify_one()/notify_all() operations. But I didn't get exactly how they are supposed to work. cppreference says:
Performs atomic waiting operations. Behaves as if it repeatedly performs the following steps:
- Compare the value representation of this->load(order) with that of old.
- If those are equal, then blocks until
*thisis notified by notify_one() or notify_all(), or the thread is unblocked spuriously.- Otherwise, returns.
These functions are guaranteed to return only if value has changed, even if underlying implementation unblocks spuriously.
I don't exactly get how these 2 parts are related to each other. Does it mean that if the value if not changed, then the function does not return even if I use notify_one()/notify_all() method? meaning that the operation is somehow equal to following pseudocode?
while (*this == val) {
// block thread
}
CodePudding user response:
Yes, that is exactly it. notify_one/all simply provide the waiting thread a chance to check the value for change. If it remains the same, e.g. because a different thread has set the value back to its original value, the thread will remain blocking.
Note: A valid implementation for this code is to use a global array of mutexes and condition_variables. atomic variables are then mapped to these objects by their pointer via a hash function. That's why you get spurious wakeups. Some atomics share the same condition_variable.
Something like this:
std::mutex atomic_mutexes[64];
std::condition_variable atomic_conds[64];
template<class T>
std::size_t index_for_atomic(std::atomic<T>* ptr) noexcept
{ return reinterpret_cast<std::size_t>(ptr) / sizeof(T) % 64; }
void atomic<T>::wait(T value, std::memory_order order)
{
if(this->load(order) != value)
return;
std::size_t index = index_for_atomic(this);
std::unique_lock<std::mutex> lock(atomic_mutexes[index]);
while(this->load(std::memory_order_relaxed) == value)
atomic_conds[index].wait(lock);
}
template<class T>
void std::atomic_notify_one(std::atomic<T>* ptr)
{
/* needs t notify_all because we could have multiple waiters
* in multiple atomics due to aliasing
*/
atomic_conds[index_for_atomic(ptr)].notify_all();
}
