只有编译器的内存屏障(如std::atomic_signal_fence)何时有用
When is a compiler-only memory barrier (such as std::atomic_signal_fence) useful?
编译器围栏的概念经常出现在我阅读有关内存模型、屏障、排序、原子学等的文章时,但通常情况下,它是在也与CPU围栏配对的上下文中出现的,正如人们所期望的那样。
然而,偶尔我会读到一些围栏结构,仅适用于编译器。这方面的一个例子是C++11 std::atomic_signal_fence
函数,它在cppreference.com上声明:
std::atomic_signal_fence等效于std::atomic_thread_fence,除了没有CPU发布了用于存储器排序的指令。仅重新排序编译器的指令被抑制为顺序指令。
我有五个问题与这个主题有关:
-
正如名称
std::atomic_signal_fence
所暗示的,异步中断(例如线程被内核抢占以执行信号处理程序)是否是仅的情况,其中仅编译器的围栏是有用的? -
它的有用性是否适用于所有体系结构,包括强序体系结构,如
x86
? -
是否可以提供一个特定的示例来证明仅编译器围栏的有用性?
-
使用
std::atomic_signal_fence
时,使用acq_rel
和seq_cst
排序有什么区别吗?(我希望它不会有什么不同。) -
第一个问题可能涵盖了这个问题,但我很好奇,无论如何都要具体问一下:是否有必要使用
thread_local
访问的围栏?(如果真的是这样的话,我希望只有编译器围栏(如atomic_signal_fence
)才是的首选工具。)
谢谢。
要回答所有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)只允许以不会从单线程角度改变程序相对于其序列点的可观察行为的方式对内存访问进行重新排序。从逻辑上讲,多线程程序中的线程局部静态与单线程程序中全局静态相同。在这两种情况下,数据只能从单个线程访问,这可以防止数据竞争的发生。
实际上有一些不可移植但有用的C编程习惯用法,其中编译器围栏是有用的,即使在多核代码中也是如此(尤其是在C11之前的代码中)。典型的情况是,程序正在进行一些通常会变得不稳定的访问(因为它们是对共享变量的),但您希望编译器能够移动访问。如果您知道访问在目标平台上是原子访问(并且您采取了一些其他预防措施),则可以使访问保持非易失性,但使用编译器屏障包含代码移动。
值得庆幸的是,大多数像这样的编程都被C11/C++11宽松的原子论所淘汰。
- 如何从 std::atomic 中提取指针 T<T>?
- std::atomic和std::condition_variable wait,notify_*方法之间的区别
- std::memory_order for std::atomic:<T>:wait
- MESI协议和std::atomic-它是否确保所有写入立即对其他线程可见?
- 在 lambda 表达式中使用 std::atomic
- C++std::atomic在程序员级别保证了什么
- 如果在 2 个线程中使用,是否值得将size_t声明为 std::atomic?
- MyType 允许 std::atomic 的确切要求是什么<MyType>?
- 无法将类型"T&"的非常量左值引用绑定到类型"T"的右值 t++ std::atomic<T>
- gcc Atomic在gcc 4.1.1中内置了奇怪的行为
- 在 C++20 之前和之后初始化 std::atomic
- Red Hat:使用<atomic>编译很好,但链接器找不到__atomic_store_16;什么库?
- std::atomic 和 std::mutex 的相对性能
- 使用用户定义的类型 UDT 实例化 std::atomic<>。如果 UDT 具有虚函数,则 l 墨水将失败。为什么?
- 简单使用 std::atomic 在两个线程之间共享数据
- 调用 raise(signal) 会终止 c++ 程序
- Port pthread_cond_broadcast to std::atomic
- std::atomic中的任何内容都是免费等待的
- 对于 CPU 无法原子操作的类型,std::atomic 有什么意义?
- 未调用 signal() 指定的处理程序