【问题标题】:How is std::atomic<T>::notify_all ordered?std::atomic<T>::notify_all 是如何排序的?
【发布时间】:2020-06-10 03:23:23
【问题描述】:

我希望下面的程序不会挂起。

如果在(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. 程序可能会挂起,我必须使用栅栏来防止它。究竟是什么围栏,在哪里以及为什么?
  2. 程序可能不会挂起。那么如何防止提到的重新排序?我问的不是实施,而是标准措辞。

【问题讨论】:

  • 整个功能感觉就像是灾难的秘诀,尤其是考虑到 ABA 漏洞。 :-)
  • 我想打电话给WaitOnAddress,我想称之为跨平台方式。我有通常的基于环形缓冲区的 SPSC 队列,所以我不害怕 ABA。所以我喜欢这个功能。
  • “常见的 Windows 事件对象咆哮” :-)
  • 在 C++20 中遇到 Windows 自动重置事件:std::binary_semaphore
  • 但是,原子等待更... 强大

标签: c++ language-lawyer c++20 memory-barriers stdatomic


【解决方案1】:

标准规定:

对原子对象M上的原子等待操作的调用有资格通过调用M上的原子通知操作来解除阻塞 > 如果在 M 上存在副作用 XY 使得:

  • 原子等待操作在观察到X的结果后已经阻塞,
  • XM的修改顺序中在Y之前,并且
  • Y 发生在调用原子通知操作之前。

关于nofify_all:

效果:解除对*ptr上所有有资格被此调用解除阻塞的原子等待操作的执行。

在您的示例中,go(M) 的初始化对应于 Xstore (2) 对应于 Y .初始化发生在调用等待之前,而存储发生在调用通知之前。存储发生在通知之前,因为它是在它之前排序的,并且两个函数都在同一个对象上操作。存储本身是否放松并不重要,因为内存顺序只对周围的操作进行排序。 [intro.races] 6.9.2.1.19 状态:

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

不幸的是,该标准在可见性方面相当模糊,但据我了解,被某些通知调用解除阻塞的等待调用可以保证观察到在该通知调用之前发生的最新更改(或稍后的值) - 只是就像条件变量的情况一样

所以不,你的程序不能挂起。

【讨论】:

猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-11-22
  • 1970-01-01
  • 2015-03-24
  • 2015-05-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-21
相关资源
最近更新 更多