This is a language-lawyer question.
First of all, does the a.wait()
in the following code always get to return?
std::atomic_int a{ 0 };
void f()
{
a.store(1, std::memory_order_relaxed);
a.notify_one();
}
int main()
{
std::thread thread(f);
a.wait(0, std::memory_order_relaxed);//always return?
thread.join();
}
I believe the standard's intention is that a.wait()
always get to return. (Otherwise atomic::wait/notify
would be useless, isn't it?) But I think the current standard text cannot guarantee this.
The relevant part of the standard is in §31.6 [atomics.wait] paragraph 4:
A call to an atomic waiting operation on an atomic object
M
is eligible to be unblocked by a call to an atomic notifying operation onM
if there exist side effectsX
andY
onM
such that:
- (4.1) — the atomic waiting operation has blocked after observing the result of
X
,- (4.2) —
X
precedesY
in the modification order ofM
, and- (4.3) —
Y
happens before the call to the atomic notifying operation.
and §31.8.2 [atomics.types.operations] paragraph 29~33:
void wait(T old, memory_order order = memory_order::seq_cst) const volatile noexcept;
void wait(T old, memory_order order = memory_order::seq_cst) const noexcept;
Effects: Repeatedly performs the following steps, in order:
- (30.1) — Evaluates
load(order)
and compares its value representation for equality against that ofold
.- (30.2) — If they compare unequal, returns.
- (30.3) — Blocks until it is unblocked by an atomic notifying operation or is unblocked spuriously.
void notify_one() volatile noexcept;
void notify_one() noexcept;
Effects: Unblocks the execution of at least one atomic waiting operation that is eligible to be unblocked (31.6) by this call, if any such atomic waiting operations exist.
With the above wording, I see two problems:
- If the
wait()
thread saw the value in step (30.1), compared it equal toold
in step (30.2), and got scheduled out; then in another threadnotify_one()
stepped in and saw no blocking thread, doing nothing; the subsequent blocking in step (30.3) would never be unblocked. Here isn't it necessary for the standard to say "wait()
function atomically performs the evaluation-compare-block operation", similar to what is said aboutcondition_variable::wait()
? - There's no synchronization between
notify_*()
and unblocking ofwait()
. If in step (30.3), the thread was unblocked by an atomic notifying operation, it would repeat step (30.1) to evaluateload(order)
. Here there is nothing preventing it from getting the old value. (Or is there?) Then it would block again. Now no one would wake it.
Is the above concern just nit-picking, or defect of the standard?
store(1)
inf
corresponds to theY
in the standard. It is sequenced before thenotify
, hence happens before thenotify
. – Keyholewait
is an atomic wait operation, and if it loads 0, then it is eligible to be unblocked by the notify. So the notify must in fact unblock it. It's the entire call towait
that's an atomic wait operation, not merely step 3 of it. – Culottesa
until it finally sees the new value. AFAICT you're supposed to be able to notify a thread even if the variable hasn't changed, and it should go back to sleep until you callnotify()
a second time. – Culottesseq_cst
would fix it. What if both the loads in the wait precede the store in the total order S? That would be extremely counterintuitive, but I don't see how to disprove it. I am beginning to wonder if #2 is indeed a defect, and that they meant to have the notify synchronize with the resulting unblock (though not with any spurious unblock). – Culottesseq_cst
would fix it. What if both the loads in the wait precede the store in the total order S? Till now, I was thinkingseq_cst
reads are like RMW operations — see the last value from the modification order of M, or at least something like the last value stored by aseq_cst
write to M. Which is not really the case… – Buttaseq_cst
. But which one is the "last value" is kind of tautological. If your load happens-before a store, then you see the old value; but even withseq_cst
the converse is not true. If the coherence ordering ona
were "initialize to 0, load 0 first time, load 0 second time, store 1" then everything is fine; the "store 1" doesn't synchronize with anything, since nobody loads the value 1. To contradict it, I think we have to show that the "store 1" happens-before the second load, which I see no way to prove. – Culotteswait
is unblocked. The thing is, evenseq_cst
doesn't require reading the value from the last operation in modification order of M. – Butta