ils*_*tam 2 c c++ memory-model memory-barriers stdatomic
我想在原子和非原子操作之间使用独立的内存屏障(我认为无论如何它都不重要)。我想我了解存储屏障和加载屏障的含义以及 4 种可能的内存重新排序;LoadLoad
, StoreStore
, LoadStore
, StoreLoad
.
但是,我总是发现获取/释放概念令人困惑。因为在阅读文档时,acquire 不仅说到loads,还说到stores,而release 不仅说到stores,还说到loads。另一方面,普通负载障碍仅为您提供负载保证,而普通商店障碍仅为您提供商店保证。
我的问题如下。在 C11/C++11 中,将独立atomic_thread_fence(memory_order_acquire)
视为负载屏障(防止LoadLoad
重新排序)和atomic_thread_fence(memory_order_release)
存储屏障(防止StoreStore
重新排序)是否安全?
如果以上是正确的,我可以用什么来防止LoadStore
和StoreLoad
重新排序?
当然,我对可移植性感兴趣,我不在乎上述在特定平台上产生什么。
不,放松负载后的获取屏障可以成为获取负载(与仅使用获取负载相比,在某些 ISA 上效率低下),因此它必须阻止 LoadStore 和 LoadLoad。
请参阅 https://preshing.com/20120913/acquire-and-release-semantics/的几个非常有用的排序图表,显示发布商店需要确保所有以前的加载和商店都是“可见的”,因此需要阻塞 StoreStore 和 LoadStore。(商店部分是第二个的重新排序)。尤其是这张图:
还有https://preshing.com/20130922/acquire-and-release-fences/
https://preshing.com/20131125/acquire-and-release-fences-dont-work-the-way-youd-expect/ 解释了 acq 和 rel栅栏的 2 向性质与 an 的 1 向性质acq 或 rel操作,如加载或存储。显然有些人对atomic_thread_fence()
保证的内容有误解,认为它太弱了。
为了完整起见,请记住,这些排序规则必须由编译器强制执行,以防止编译时重新排序,而不仅仅是运行时。
考虑在 C++ 抽象机中对 C++ 加载/存储起作用的障碍可能最有效,而不管它是如何在 asm 中实现的。但也有像 PowerPC 这样的极端情况,其中心理模型并没有涵盖所有内容(IRIW 重新排序,见下文)。
我确实建议尝试从获取和释放操作的角度考虑,以确保其他操作彼此可见,并且绝对不要编写仅使用宽松操作和单独障碍的代码。 这可能是安全的,但通常效率较低。
关于 ISO C/C++ 内存/线程间排序的所有内容都是根据从发布存储中看到值的获取负载来正式定义的,从而创建“同步”关系,而不是关于控制本地重新排序的围栏。
std::atomic
并没有明确保证连贯共享内存状态的存在,所有的线程看到在同一时间的变化。在您使用的心智模型中,在读取/写入单个共享状态时进行本地重新排序,当一个线程使其存储对其他一些线程可见,然后对所有其他线程全局可见时,就会发生 IRIW 重新排序。(在某些 SMT PowerPC CPU 上可能会在实践中发生这种情况。)。
在实践中,所有 C/C++ 实现都跨内核运行线程,这些内核确实具有共享内存的缓存一致性视图,因此在读/写一致性共享内存方面的心理模型具有控制本地重新排序的障碍。但请记住,C++ 文档不会讨论重新排序,而只会讨论是否首先保证任何顺序。
要深入了解 C++ 如何描述内存模型与如何描述真实架构的 asm 内存模型之间的区别,另请参阅如何在 C++11 中实现 StoreLoad 屏障?(包括我在那里的回答)。也不会atomic_thread_fence(memory_order_seq_cst)有一个完整的内存屏障的语义?相关的。
fence(seq_cst)
包括 StoreLoad(如果该概念甚至适用于给定的 C++ 实现)。我认为根据局部障碍进行推理,然后将其转换为 C++大多有效,但请记住,它并没有对 C++ 允许的 IRIW 重新排序的可能性进行建模,这在某些 POWER 硬件的现实生活中会发生。
还要记住,这var.load(acquire)
可能比var.load(relaxed); fence(acquire);
某些 ISA(特别是 ARMv8)更有效。
例如Godbolt 上的这个例子,由 GCC8.2 为 ARMv8 编译-O2 -mcpu=cortex-a53
#include <atomic>
int bad_acquire_load(std::atomic<int> &var){
int ret = var.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire);
return ret;
}
bad_acquire_load(std::atomic<int>&):
ldr r0, [r0] // plain load
dmb ish // FULL BARRIER
bx lr
Run Code Online (Sandbox Code Playgroud)
int normal_acquire_load(std::atomic<int> &var){
int ret = var.load(std::memory_order_acquire);
return ret;
}
normal_acquire_load(std::atomic<int>&):
lda r0, [r0] // acquire load
bx lr
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
368 次 |
最近记录: |