C++ Logo

std-discussion

Advanced search

Re: Synchronization of atomic notify and wait

From: Andrey Semashev <andrey.semashev_at_[hidden]>
Date: Mon, 18 Jul 2022 21:29:04 +0300
On 7/18/22 20:40, Nate Eldredge wrote:
> On Mon, 18 Jul 2022, Andrey Semashev via Std-Discussion wrote:
>
>> On 7/18/22 17:48, Nate Eldredge via Std-Discussion wrote:
>
>>> Suppose that `b.wait(false)` starts while the value of `b` is still
>>> `false`, and so it blocks. When `b.notify_one()` executes, the main
>>> thread will unblock and test the value of `b`. However, I cannot find
>>> anything to clearly imply that the store of `true` to `b` will be
>>> visible by then. If the main thread loads the old value `false`, it
>>> will block again, and potentially never wake up (unless there is a
>>> spurious unblock).
>>
>> What you're saying is only possible if notify_one is observed before the
>> store by the main thread. And that is not possible as the store is
>> sequenced-before the notify. Consequently, the store happens-before the
>> notify:
>>
>> http://eel.is/c++draft/intro.multithread#intro.races-10
>
> I agree that the store happens-before notify_one() by sequencing, but I
> don't see how that helps by itself. What we need is for the store in
> thr1 to happen-before the load inside b.wait() in the main thread.
> Since these are in separate threads, sequencing alone cannot get us that
> relation.

If the store is not observed by the load then wait blocks until
notify_one. Note that this load-test-and-block is atomic. Then the
notify_one will unblock the wait when it happens.

If you're asking whether the standard guarantees that loads observe the
stores then this is guaranteed here:

http://eel.is/c++draft/atomics.order#3
http://eel.is/c++draft/atomics.order#10
http://eel.is/c++draft/atomics.order#11

Also, the unblocking is described here:

http://eel.is/c++draft/atomics.wait#4

> By my reading of [data.races p7-12], if we disregard consume operations,
> the only way to get a happens-before between two evaluations in
> different threads is to have a synchronizes-with somewhere in the
> chain. The example program doesn't have any synchronizes-with relations
> that I can find, so I don't see how we can ever establish the thr1 store
> to happen-before the main thread load.

Synchronizes-with is not the only relation between events in different
threads. As shown above, if the store is coherence-ordered before the
load then the load will observe its effect. Since the store and notify
effects are observed in that order, and wait is atomic, there is no way
that wait does not observe the store and yet is woken by the notify.

>> Synchronizes-with is a different relation that describes memory
>> ordering wrt. other objects than the atomic. Notifying operations
>> don't do that and don't need to do that because the preceding store
>> does that. I.e. it is the store that may synchronize-with the load
>> performed by the waiting operation.
>
> Well, I think it's analogous to the way that `std::thread t(fn)`
> synchronizes-with the start of the evaluation of `fn` in the new thread
> (http://eel.is/c++draft/thread.thread.constr#6). That is how, when we
> do something like
>
> std::atomic<int> x;
> void fn() {
> std::cout << x.load(std::memory_order_relaxed) << std::endl;
> }
> int main() {
> x.store(42, std::memory_order_relaxed);
> std::thread t(fn);
> t.join();
> }
>
> we are guaranteed to have the value 42 printed. x.store happens-before
> the thread constructor (sequencing), which happens-before the start of
> fn() (synchronizes-with), which happens-before x.load() (sequencing),
> and so by WR coherency the value 42 must be loaded.
>
> I think that notify/wait needs something similar to guarantee the
> desired deadlock-free behavior, and I don't see it in the current
> standard. There is nothing to play the role of the synchronizes-with step.

If you want to have a synchronizes-with relation then use store(release)
and wait(acquire). Any sort of memory fence in notify is redundant.

Received on 2022-07-18 18:29:13