35

当我阅读有关内存模型、障碍、排序、原子等的内容时,经常会出现编译器栅栏的概念,但通常它CPU 栅栏配对,但正如人们所期望

然而,有时我会读到适用于编译器的栅栏结构。这方面的一个例子是 C++11std::atomic_signal_fence函数,它在cppreference.com上声明:

std::atomic_signal_fence 等价于 std::atomic_thread_fence,除了不发出内存排序的 CPU 指令。仅编译器对指令的重新排序被抑制为 order instructs。

我有五个与此主题相关的问题:

  1. 顾名思义std::atomic_signal_fence异步中断(例如线程被内核抢占以执行信号处理程序)是仅编译器围栏有用的唯一情况吗?

  2. 它的用处是否适用于所有架构,包括强排序架构,例如x86

  3. 是否可以提供一个具体的示例来演示仅编译器围栏的有用性?

  4. 使用时,使用和订购std::atomic_signal_fence有什么区别吗?(我希望它没有任何区别。)acq_relseq_cst

  5. 第一个问题可能涵盖了这个问题,但我很好奇,无论如何都要特别问一下:是否有必要使用带有thread_local访问权限的栅栏?(如果有的话,我希望编译器专用的栅栏成为atomic_signal_fence首选工具。)

谢谢你。

4

2 回答 2

25

要回答所有 5 个问题:


1)编译器栅栏(本身,没有 CPU 栅栏)仅在两种情况下有用:

  • 在单个线程和绑定到同一线程的异步中断处理程序(例如信号处理程序)之间强制执行内存顺序约束。

  • 当保证每个线程都将在同一个 CPU 内核上执行时,在多个线程之间强制执行内存顺序约束 。换句话说,应用程序只会在单核系统上运行,或者应用程序采取特殊措施(通过处理器亲和性)来确保共享数据的每个线程都绑定到同一个核心。


2) 底层架构的内存模型,无论是强序还是弱序,都与在某种情况下是否需要编译器围栏无关。


3)这是一个伪代码,它演示了编译器栅栏本身的使用,以充分同步线程和绑定到同一线程的异步信号处理程序之间的内存访问:

void async_signal_handler()
{
    if ( is_shared_data_initialized )
    {
        compiler_only_memory_barrier(memory_order::acquire);
        ... use shared_data ...
    }
}

void main()
{
// initialize shared_data ...
    shared_data->foo = ...
    shared_data->bar = ...
    shared_data->baz = ...
// shared_data is now fully initialized and ready to use
    compiler_only_memory_barrier(memory_order::release);
    is_shared_data_initialized = true;
}

重要说明:此示例假定async_signal_handler绑定到初始化shared_data和设置is_initialized标志的同一线程,这意味着应用程序是单线程的,或者它相应地设置线程信号掩码。否则,编译器栅栏就不够用了,还需要一个CPU 栅栏。


4)它们应该是相同的。 acq_rel并且seq_cst都应该产生一个完整的(双向)编译器围栏,不会发出与围栏相关的 CPU 指令。“顺序一致性”的概念只有在涉及多个内核和线程时才会发挥作用,并且atomic_signal_fence只适用于一个执行线程。


5)不。(当然,除非线程本地数据是从异步信号处理程序访问的,在这种情况下可能需要编译器围栏。)否则,线程本地数据永远不需要围栏,因为编译器(和 CPU ) 只允许以不改变程序从单线程角度看其序列点的可观察行为的方式对内存访问进行重新排序。并且可以逻辑地认为多线程程序中的线程局部静态与单线程程序中的全局静态相同。在这两种情况下,数据只能从单个线程访问,从而防止发生数据竞争。

于 2013-08-27T00:04:22.367 回答
3

实际上有一些不可移植但有用的 C 编程习惯,其中编译器围栏很有用,即使在多核代码中(尤其是在 C11 之前的代码中)也是如此。典型的情况是程序正在执行一些通常会变为 volatile 的访问(因为它们是共享变量),但您希望编译器能够移动访问。如果您知道访问在目标平台上是原子的(并且您采取了一些其他预防措施),则可以使访问保持非易失性,但使用编译器屏障包含代码移动。

值得庆幸的是,大多数这样的编程都因 C11/C++11 宽松原子而过时。

于 2013-11-05T09:19:35.063 回答