首页 > 解决方案 > std::atomic 怎么样::notify_all 已订购?

问题描述

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

如果在(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. 程序可能不会挂起。那么如何防止提到的重新排序?我不是在问实施,而是在问标准措辞。

标签: c++language-lawyerc++20memory-barriersstdatomic

解决方案


该标准规定:

如果在M上存在副作用XY ,则对原子对象M上的原子等待操作的调用可以通过调用M上的原子通知操作来解除阻塞,这样:

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

关于nofify_all

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

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

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

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

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


推荐阅读