当我正在阅读有关内存模型,障碍,排序,原子等的内容时,经常出现编译器栏的概念,但通常它也在的上下文中>正如人们所期望的那样,em>与 CPU fence 配对。
但是,我偶尔会阅读有关仅应用于编译器的fence构造。这方面的一个例子是C ++ 11 std::atomic_signal_fence
函数,该函数在cppreference.com:
std :: atomic_signal_fence相当于std :: atomic_thread_fence,除了没有CPU 发出内存排序说明。只重新排序了 编译器的指令按订单指示被禁止。
我有五个与此主题相关的问题:
正如名称std::atomic_signal_fence
所暗示的那样,异步中断(例如内核被内核抢占以执行信号处理程序) only < / strong> 仅编译器栅栏有用的情况?
它的用处是否适用于所有体系结构,包括强烈排序的,例如x86
?
是否可以提供特定的示例来演示仅编译器围栏的用途?
使用std::atomic_signal_fence
时,使用acq_rel
和seq_cst
排序之间有什么区别吗? (我希望它没有任何区别。)
第一个问题可能会涵盖这个问题,但我很好奇,无论如何都要特别询问它:使用thread_local
次访问的围栏是否曾经 ? (如果有的话,我希望仅编译围栏,例如atomic_signal_fence
成为首选工具。)
谢谢。
答案 0 :(得分:21)
回答所有5个问题:
1)编译器围栏(本身,没有CPU围栏)仅在两个情况下有用:
在绑定到同一个线程(例如信号处理程序)的单个线程和异步中断处理程序之间强制执行内存顺序约束 。
在保证每个线程将在同一个CPU核心上执行时,在多个线程之间强制执行内存顺序约束 。换句话说,应用程序将仅在single core系统上运行,或者应用程序采取特殊措施(通过processor affinity)以确保共享数据的每个线程都绑定到同一个核心。
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 fence 。
4)它们应该是相同的。 acq_rel
和seq_cst
都应该产生一个完整的(双向)编译器围栏,不会发出与围栅相关的CPU指令。 “顺序一致性”的概念仅在涉及多个核心和线程时才会发挥作用,而atomic_signal_fence
仅适用于一个执行线程。
5) No。(当然,除非从异步信号处理程序访问线程本地数据,否则可能需要编译器围栏。)否则,永远不需要使用围栏线程本地数据,因为编译器(和CPU)只允许以不从单线程角度改变程序相对于sequence points的可观察行为的方式重新排序内存访问。从逻辑上讲,可以将多线程程序中的线程局部静态视为与单线程程序中的全局静态相同。在这两种情况下,只能从单个线程访问数据,这可以防止发生数据争用。
答案 1 :(得分:2)
实际上有一些非便携但有用的C编程习惯用法,编译器围栏很有用,即使在多核代码中(特别是在C11前代码中)。典型情况是程序正在进行一些通常会变为易失性的访问(因为它们是共享变量),但是您希望编译器能够移动访问。如果您知道访问在目标平台上是原子的(并且您采取了一些其他预防措施),则可以使访问保持非易失性,但包含使用编译器障碍的代码移动。
值得庆幸的是,大多数这样的编程都被C11 / C ++ 11轻松原子所淘汰。