zwh*_*nst 13 c++ atomic language-lawyer stdatomic c++20
这是一个语言律师问题。
\n首先,a.wait()下面的代码是否总是返回?
std::atomic_int a{ 0 };\n\nvoid f()\n{\n a.store(1, std::memory_order_relaxed);\n a.notify_one();\n}\nint main()\n{\n std::thread thread(f);\n\n a.wait(0, std::memory_order_relaxed);//always return?\n\n thread.join();\n}\nRun Code Online (Sandbox Code Playgroud)\n我相信标准的目的是a.wait()总是得到回报。(否则atomic::wait/notify就没用了,不是吗?)但我认为目前的标准文本不能保证这一点。
标准的相关部分位于 \xc2\xa731.6 [atomics.wait] 第 4 段:
\n\n\n对原子对象上的原子等待操作的调用
\nM可以通过调用原子通知操作来解除阻塞M,如果存在副作用X,并且Y满足M以下条件:\n
\n- (4.1) \xe2\x80\x94 观察结果后,原子等待操作已阻塞
\nX,- (4.2) \xe2\x80\x94在,的修改顺序
\nX之前YM- (4.3) \xe2\x80\x94
\nY发生在调用原子通知操作之前。
和 \xc2\xa731.8.2 [atomics.types.operations] 第 29~33 段:
\n\n\n\n
void wait(T old, memory_order order = memory_order::seq_cst) const volatile noexcept;\n
void wait(T old, memory_order order = memory_order::seq_cst) const noexcept;效果:按顺序重复执行以下步骤:
\n\n
\n- (30.1) \xe2\x80\x94 评估
\nload(order)并比较其值表示与 的值表示是否相等old。- (30.2) \xe2\x80\x94 如果比较不相等,则返回。
\n- (30.3) \xe2\x80\x94 阻塞,直到被原子通知操作解除阻塞或虚假解除阻塞为止。
\n\n
void notify_one() volatile noexcept;\n
void notify_one() noexcept;效果:取消阻止至少一个有资格通过此调用取消阻止的原子等待操作 (31.6) 的执行(如果存在任何此类原子等待操作)。
\n
通过上面的措辞,我看到两个问题:
\nwait()线程看到步骤(30.1)中的值,将其与old步骤(30.2)中的值进行比较,并被调度出去;然后在另一个线程中notify_one()介入并看到没有阻塞线程,什么也不做;步骤(30.3)中的后续阻塞将永远不会被解除阻塞。标准是否有必要说“wait()函数原子地执行评估比较块操作”,类似于所说的condition_variable::wait()?notify_*()和解锁wait()。如果在步骤(30.3)中,线程被原子通知操作解除阻塞,则它将重复步骤(30.1)来评估load(order)。这里没有什么可以阻止它获得旧值。(或者有吗?)然后它会再次阻塞。现在没有人会叫醒它了。上述担忧只是挑剔,还是标准的缺陷?
\n#1 几乎是由C++20 线程解决的,可能会永远等待 std::atomic。该wait()操作显然有资格被 解锁notify(),并且是唯一的此类操作,因此notify()必须解锁它。合格的“等待操作”是整个调用,而不仅仅是步骤30.3。
如果实现以非原子方式执行步骤 30.1-3,使得通知可以在步骤 1 和步骤 3“之间”发生,那么它必须以某种方式确保步骤 3 无论如何都能解除阻塞。
#2 更粘。在这一点上我认为你是对的:标准并不能保证第二次加载得到值1;如果没有,那么它可能会再次阻塞并且永远不会被唤醒。
在此示例中,内存排序的使用relaxed非常清楚。如果我们想证明第二次加载必须看到 1,我能看到的唯一方法是调用写读一致性(intro.races p18),这要求我们证明存储发生在加载之前,从介绍的意义上来说。比赛 p10。这反过来又要求在这一过程中的某个地方,我们在一个线程中进行一些操作,与另一个线程中的某些操作同步(如果没有同步,则无法在之前发生线程间操作,除非存在不属于该线程的消耗操作)案例在这里)。通常,您可以从获取负载与发布存储(atomics.order p2)的配对中获得同步,但这里没有这样的东西;据我所知,也没有其他任何可以同步的东西。所以我们没有证据。
事实上,我认为即使我们升级到seq_cst运营,问题仍然存在。然后,我们可以在存储之前对两个加载进行连贯排序,并且atomics.order p4 的总顺序S 将是“第一次加载,第二次加载,存储”。我不认为这有任何矛盾。我们仍然需要显示一个同步来排除这种情况,但我们又不能。看起来可能比这种情况有更好的机会relaxed,因为seq_cst加载和存储分别是获取和释放。但使用此功能的唯一方法是其中一个负载要从存储中获取其值,即如果其中一个负载要返回 1,而我们假设情况并非如此。因此,这种不良行为似乎再次符合所有规则。
它确实让您想知道标准作者是否打算要求通知与解锁同步。这将解决问题,而且我猜想现实生活中的实现已经包含了必要的障碍。
但事实上,我没有在任何地方看到这一点。
我能看到的唯一可能的出路是“有资格被解锁”适用于整个等待操作,而不仅仅是它的单个迭代。但似乎很明显,其意图是,如果您被通知解除阻塞并且值没有更改,那么您会再次阻塞,直到发生第二个通知(或虚假唤醒)。
在我看来,它开始像是一个缺陷。
| 归档时间: |
|
| 查看次数: |
1341 次 |
| 最近记录: |