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?