【发布时间】:2022-01-10 15:39:57
【问题描述】:
这是一个语言律师问题。
首先,下面代码中的a.wait() 是否总能返回?
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();
}
我相信标准的意图是a.wait() 总是可以返回。 (否则atomic::wait/notify就没有用了,不是吗?)但我认为目前的标准文本不能保证这一点。
标准的相关部分在 §31.6 [atomics.wait] 第 4 段:
如果存在副作用
X和Y上M这样:
- (4.1)——原子等待操作在观察
X的结果后阻塞,- (4.2) — 在
M的修改顺序中,X在Y之前,并且- (4.3) —
Y发生在调用原子通知操作之前。
和§31.8.2 [atomics.types.operations] 第 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;效果:按顺序重复执行以下步骤:
- (30.1) - 评估
load(order)并将其值表示与old的值表示进行比较。- (30.2) — 如果它们比较不相等,则返回。
- (30.3) — 阻塞,直到它被原子通知操作解除阻塞或被虚假解除阻塞。
void notify_one() volatile noexcept;
void notify_one() noexcept;效果:如果存在任何此类原子等待操作,则取消阻止至少一个有资格通过此调用解除阻塞 (31.6) 的原子等待操作的执行。
通过上述措辞,我看到了两个问题:
- 如果
wait()线程在步骤(30.1)中看到了值,则将其与步骤(30.2)中的old进行比较,并被调度出去;然后在另一个线程notify_one()介入,没有看到阻塞线程,什么都不做;步骤 (30.3) 中的后续阻塞将永远不会被解除阻塞。这里不是需要标准说“wait()function atomically执行评估-比较-块操作”,类似于condition_variable::wait()的说法吗? -
notify_*()和解除阻止wait()之间没有同步。如果在步骤 (30.3) 中,线程被原子通知操作解除阻塞,它将重复步骤 (30.1) 以评估load(order)。这里没有什么可以阻止它获得旧值。 (或者有吗?)然后它会再次阻塞。现在没有人会唤醒它。
以上问题只是吹毛求疵,还是标准的缺陷?
【问题讨论】:
-
@NateEldredge,我认为
f中的store(1)对应于标准中的Y。它在notify之前排序,因此发生在notify之前。 -
哦,完全正确。我看错了。
-
那么我看不出有什么问题。
wait是一个原子等待操作,如果它加载 0,那么它就有资格被通知解除阻塞。因此,通知实际上必须取消阻止它。这是对wait的整个调用,这是一个原子等待操作,而不仅仅是它的第 3 步。 -
@NateEldredge 问题 №2 怎么样?
-
2 号比较棘手,我同意...
标签: c++ multithreading language-lawyer atomic c++20