因此,我偶然发现了 Jeff Preshing 的精彩博客文章,其中介绍了什么是Acquire
CPU障碍Release
以及如何通过一些 CPU障碍实现这些障碍。
我还读过关于某种总顺序的文章,它保证与非一致性后关系SeqCst
一致- 尽管有时由于历史原因它可能与由简单/操作建立的前发生关系相矛盾。Acquire
Release
我的问题是,旧的 GCC 内置函数如何映射到 C++11 (及更高版本) 提出的内存模型?具体来说,如何映射__sync_synchronize()
到 C++11 或更高版本的现代 C/C++?
在 GCC手册中,此调用被简单地描述为完整内存屏障,我认为这是所有四种主要屏障(即LoadLoad
/ LoadStore
/ StoreLoad
/StoreStore
屏障)的组合。但sync_synchronize
相当于std::atomic_thread_fence(memory_order_seq_cst)
?或者,从形式上讲,其中一个比另一个更强(我想这里就是这种情况:通常,SeqCst
隔离应该更强,因为它需要工具链/平台以某种方式即兴执行全局排序,不是吗?),而碰巧大多数 CPU只提供同时满足两者(完整内存屏障__sync_synchronize
,完全顺序排序std::atomic_thread_fence(memory_order_seq_cst)
)的指令,例如 x86和 PowerPC ?mfence
hwsync
__sync_synchronize
和要么std::atomic_thread_fence(memory_order_seq_cst)
在形式上相等,要么在实际上相等(即,从形式上讲它们是不同的,但没有商业化的 CPU 会费心区分两者),从技术上讲,memory_order_relaxed
对同一原子的负载仍然不能依赖于与它同步/创建先发生关系,不是吗?
也就是说,从技术上讲,所有这些断言都可以失败,对吗?
// Experiment 1, using C11 `atomic_thread_fence`: assertion is allowed to fail, right?
// global
static atomic_bool lock = false;
static atomic_bool critical_section = false;
// thread 1
atomic_store_explicit(&critical_section, true, memory_order_relaxed);
atomic_thread_fence(memory_order_seq_cst);
atomic_store_explicit(&lock, true, memory_order_relaxed);
// thread 2
if (atomic_load_explicit(&lock, memory_order_relaxed)) {
// We should really `memory_order_acquire` the `lock`
// or `atomic_thread_fence(memory_order_acquire)` here,
// or this assertion may fail, no?
assert(atomic_load_explicit(&critical_section, memory_order_relaxed));
}
// Experiment 2, using `SeqCst` directly on the atomic store
// global
static atomic_bool lock = false;
static atomic_bool critical_section = false;
// thread 1
atomic_store_explicit(&critical_section, true, memory_order_relaxed);
atomic_store_explicit(&lock, true, memory_order_seq_cst);
// thread 2
if (atomic_load_explicit(&lock, memory_order_relaxed)) {
// Again we should really `memory_order_acquire` the `lock`
// or `atomic_thread_fence(memory_order_acquire)` here,
// or this assertion may fail, no?
assert(atomic_load_explicit(&critical_section, memory_order_relaxed));
}
// Experiment 3, using GCC built-in: assertion is allowed to fail, right?
// global
static atomic_bool lock = false;
static atomic_bool critical_section = false;
// thread 1
atomic_store_explicit(&critical_section, true, memory_order_relaxed);
__sync_synchronize();
atomic_store_explicit(&lock, true, memory_order_relaxed);
// thread 2
if (atomic_load_explicit(&lock, memory_order_relaxed)) {
// we should somehow put a `LoadLoad` memory barrier here,
// or the assert might fail, no?
assert(atomic_load_explicit(&critical_section, memory_order_relaxed));
}
我已经在 RPi 5 上尝试了这些代码片段,但我没有看到断言失败。是的,这并没有正式证明任何事情,但它也没有阐明__sync_synchronize
和之间的区别std::atomic_thread_fence(memory_order_seq_cst)
。
是的,
__sync_synchronize()
至少在实践中相当于std::atomic_thread_fence(memory_order_seq_cst)
。正式地,
__sync_synchronize()
按照内存屏障和阻塞内存重新排序进行操作,因为它早于 C++11 的正式内存模型的存在。atomic_thread_fence
按照 C++11 的内存模型进行操作;编译为全屏障指令是一个实现细节。例如,标准并不要求
thread_fence
在没有任何std::atomic<>
对象的程序中执行任何操作,因为其行为仅以原子方式定义。虽然__sync_synchronize()
(thread_fence
在实践中作为 GCC/clang 中的实现细节)可以让您在纯变量同步方面有所突破int
。这是 C++11 中的 UB,甚至对于 GCC 等已知实现而言也是一个坏主意;请参阅谁害怕一个糟糕的优化编译器?std::atomic
关于当您仅使用内存屏障而不是使用共享变量来阻止编译器将它们保存在寄存器中时可能发生的明显与不明显的不良情况(如发明的负载)relaxed
。但我的观点是,实际上它们的工作原理相同,但它们来自不同的内存模型:
__sync
内置函数是针对对缓存一致性共享内存的本地重新排序访问的障碍(即 CPU 架构视图),而 C++11std::atomic
内容是针对其修改顺序和同步/先发生的形式主义。这正式允许一些在使用缓存一致性共享内存的真实 CPU 上不合理的事情。是的,在您的代码块中,断言可能会在可以进行 LoadLoad 重新排序的 CPU 上失败。如果两个变量都位于同一缓存行中,则可能无法进行断言。请参阅C++ 原子变量内存顺序问题无法重现 LoadStore 重新排序示例,了解尝试重现内存重新排序的另一种情况。