何时只有编译器的内存屏障(例如std :: atomic_signal_fence)有用吗?

eth*_*ice 32 c++ atomic memory-fences memory-barriers c++11

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

但是,偶尔我会读到适用于编译器的fence构造.一个例子是C++ 11 std::atomic_signal_fence函数,它在cppreference.com上声明:

std :: atomic_signal_fence等效于std :: atomic_thread_fence,除了没有发出内存排序的CPU指令.仅按顺序指示抑制编译器对指令的重新排序.

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

  1. 正如名称所暗示的那样std::atomic_signal_fence,是一个异步中断(例如一个被内核抢占以执行信号处理程序的线程)唯一一种只有编译器的栅栏才有用的情况?

  2. 它的用处是否适用于所有体系结构,包括强烈排序的体系结构x86

  3. 是否可以提供一个特定的示例来演示仅编译器栅栏的用途?

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

  5. 这个问题可能是由第一个问题所覆盖,但我足够的好奇,一下也无妨具体问:是否曾经需要使用围栏与thread_local访问?(如果有的话,我希望只有编译器的围栏atomic_signal_fence才能成为首选工具.)

谢谢.

Mik*_*sar 20

要回答所有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;
}
Run Code Online (Sandbox Code Playgroud)

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


4)他们应该是一样的. acq_rel并且seq_cst都应该生成一个完整的(双向)编译器围栏,不会发出与围栅相关的CPU指令."顺序一致性"的概念仅在涉及多个核心和线程时才起作用,并且atomic_signal_fence仅涉及一个执行线程.


5)否.(当然,除非从异步信号处理程序访问线程本地数据,否则可能需要编译器栏.)否则,自编译器(和CPU)以来,永远不需要使用线程局部数据的围栏)只被允许重新排序存储器访问在不相对于其改变程序的可观察行为的方式顺序点从单线程透视.从逻辑上讲,可以将多线程程序中的线程局部静态视为与单线程程序中的全局静态相同.在这两种情况下,只能从单个线程访问数据,这可以防止发生数据争用.