C11 Standalone 内存屏障 LoadLoad StoreStore LoadStore StoreLoad

C11 Standalone memory barriers LoadLoad StoreStore LoadStore StoreLoad

我想在原子操作和非原子操作之间使用 standalone 内存屏障(我认为这根本不重要)。我想我理解存储屏障和加载屏障的含义以及 4 种可能的内存重新排序类型; LoadLoadStoreStoreLoadStoreStoreLoad

然而,我总是觉得 acquire/release 概念令人困惑。因为看文档的时候,acquire不光说加载,还要说stores,release不光说stores,还要说load。另一方面,plain load barrier 只为您提供负载保证,而 plain store barrier 只为您提供存储保证。

我的问题如下。在 C11/C++11 中,将独立的 atomic_thread_fence(memory_order_acquire) 视为负载屏障(防止 LoadLoad 重新排序)并将 atomic_thread_fence(memory_order_release) 视为存储屏障(防止 StoreStore 重新排序)?

如果以上内容正确,我可以使用什么来防止 LoadStoreStoreLoad 重新排序?

当然我对可移植性很感兴趣,我不关心以上在特定平台上产生的结果。

不,一个获取障碍在松弛加载之后可以变成获取负载(与仅使用获取负载相比,在某些 ISA 上效率低下),所以它 必须阻止 LoadStore 以及 LoadLoad.

请参阅 https://preshing.com/20120913/acquire-and-release-semantics/ 以获取一些非常有用的顺序图表,这些图表表明发布存储需要确保所有先前的加载和存储都是 "visible",因此需要阻止 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 fences 的双向性质与 acq 或 rel operation 的单向性质=71=] 就像加载或存储。显然有些人对atomic_thread_fence()保证的内容有误解,认为它太弱了。

为了完整起见,请记住这些排序规则必须由编译器针对 compile-time reordering 强制执行,而不仅仅是运行时。

考虑作用于 C++ 抽象机中的 C++ 加载/存储的障碍可能最有效,不管它在 asm 中是如何实现的。但也有像 PowerPC 这样的极端情况,其中心智模型并不能涵盖所有内容(IRIW 重新排序,见下文)。

我确实建议尝试从获取和释放操作的角度来考虑,以确保其他操作彼此可见,并且 绝对不要编写仅使用宽松操作和单独屏障的代码。 这可能是安全的,但通常效率较低。


有关 ISO C/C++ 内存/线程间排序的所有内容均根据获取负载从发布存储中查看值的方式正式定义,从而创建 "synchronizes with" 关系,而不是关于控制本地重新排序的围栏。

std::atomic 确实 而不是 明确保证所有线程同时看到更改的一致共享内存状态的存在。在您使用的心智模型中,当 reading/writing 到单个共享状态时进行本地重新排序,当一个线程使其存储对之前的 某些 其他线程可见时,可能会发生 IRIW 重新排序它们对所有其他线程全局可见。 (可以 )。

在实践中 all C/C++ implementations run threads across cores that do have a cache-coherent view of shared memory 因此根据 read/write 的心智模型通过障碍来控制本地重新排序的连贯共享内存起作用。但请记住,C++ 文档不会谈论 re-ordering,只是讨论是否首先保证任何顺序。


要深入了解 C++ 如何描述内存模型与如何描述实际架构的 asm 内存模型之间的区别,另请参阅 (including my answer there). Also Does atomic_thread_fence(memory_order_seq_cst) have the semantics of a full memory barrier? 相关内容。

fence(seq_cst) 包括 StoreLoad(如果该概念甚至适用于给定的 C++ 实现)。我认为根据局部障碍进行推理然后将其转换为 C++ 大多数 是可行的,但请记住,它没有模拟 C++ 允许的 IRIW 重新排序的可能性,这在现实生活中发生在某些 POWER 硬件上。

还要记住,var.load(acquire) 在某些 ISA 上比 var.load(relaxed); fence(acquire); 效率更高,尤其是 ARMv8。

例如this example on 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
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