std::atomic<T>::notify_all 的顺序如何?

4
我希望以下程序不会挂起。
如果在(1)中反过来观察(2)和(3),可能由于丢失通知而导致挂起:
#include <atomic>
#include <chrono>
#include <thread>


int main()
{
    std::atomic<bool> go{ false };

    std::thread thd([&go] {
        go.wait(false, std::memory_order_relaxed); // (1)
    });

    std::this_thread::sleep_for(std::chrono::milliseconds(400));

    go.store(true, std::memory_order_relaxed); // (2)
    go.notify_all();                           // (3)

    thd.join();

    return 0;
}

那么问题就是这里会发生什么:

  1. 程序可能会挂起,我必须使用"栅栏(fences)"来防止它。"栅栏(fences)"到底是什么,放在哪里以及为什么需要呢?
  2. 程序可能不会挂起。那么如何防止所提到的重排序(reordering)?我不是在问具体实现,而是在问标准措辞。

整个功能看起来像是一场灾难的配方,特别是考虑到 ABA 漏洞。 :-) - oakad
我想调用WaitOnAddress,并且希望以跨平台的方式调用它。我有通常基于环形缓冲区的SPSC队列,因此我不担心ABA问题。所以我喜欢这个特性。 - Alex Guteniev
1
通常的Windows事件对象抱怨 - oakad
在C++20中认识Windows自动重置事件:std::binary_semaphore - Alex Guteniev
但是嘿,原子等待甚至更加...强大 - Alex Guteniev
@oakad,我不理解你关于ABA漏洞的评论?如果你使用轮询而不是新的wait/notify函数,你将面临完全相同的问题,因此你仍然需要采取措施来防止ABA问题。 - mpoeter
1个回答

3
标准规定:
在原子对象M上调用原子等待操作,如果存在对M的副作用X和Y,则可以通过对M的原子通知操作来解除对M的原子等待操作的阻塞,条件如下:
- 在观察到X的结果后,原子等待操作已被阻塞 - X在M的修改顺序中先于Y - Y在对M的原子通知操作之前发生
关于nofify_all:
效果:解除所有对*ptr的原子等待操作的阻塞,这些操作符合此调用的资格。
在你的示例中,go(M)的初始化对应于X,而store (2)对应于Y。初始化发生在wait调用之前,存储发生在notify调用之前。存储之所以发生在notify之前,是因为它在序列之前,并且两个函数都作用于同一个对象。即使存储本身是松散的,由于内存顺序仅对周围操作进行排序,因此并不重要。[intro.races] 6.9.2.1.19声明:

[..]协调要求有效地禁止编译器将原子操作重新排序为单个对象,即使两个操作都是放松负载。这实际上使大多数硬件提供的缓存一致性保证可用于C++原子操作。

不幸的是,标准在可见性方面相当模糊,但据我所知,由某些notify调用解除阻塞的wait调用保证观察到在该notify调用之前发生的最新更改(或某个更晚的值),就像条件变量的情况一样。

因此,不,您的程序不会挂起。


评论不适合进行长时间的讨论;此对话已被移至聊天室 - Samuel Liew

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接