x86 上是否需要 std::memory_order_acquire 栅栏?

HCS*_*CSF 0 c++ x86 memory-barriers c++11 stdatomic

鉴于 x86 具有强大的内存模型,是否有必要使用std::memory_order_acquirefence(不是 operation)?

例如,如果我有这个代码:

uint32_t read_shm(const uint64_t offset) {
   // m_head_memory_location is char* pointing to the beginning of a mmap-ed named shared memory segment
   // a different process on different core will write to it.
   return *(uint32_t*)(m_head_memory_location + offset);
}
....
int main() {
     uint32_t val = 0;
     while (0 != (val = shm.read(some location)));
     .... // use val
}
Run Code Online (Sandbox Code Playgroud)

我真的需要std::atomic_thread_fence(std::memory_order_acquire)在return语句之前吗?

我觉得没有必要,因为上面代码的目标是尝试从 读取前 4 个字节m_head_memory_location + offset,因此重新排序栅栏后的任何内存操作都不会影响结果。

或者有一些副作用使获取栅栏变得必要?

在 x86 上是否需要获取栅栏(不是操作)?

欢迎任何意见。

Pet*_*des 5

return *(uint32_t*)(m_head_memory_location + offset);

您强制转换为atomic非非volatile uint32_t*和取消引用!!!

编译器可以假设这个uint32_t对象不是由其他任何东西写入的(即假设没有数据竞争 UB),因此它可以并将负载提升到循环之外,有效地将其转换为类似if((val=load) == 0) infinite_loop();.

GCC 内存屏障将强制重新加载,但这是std::atomic_thread_fence(std::memory_order_acquire). 对于 x86,该屏障只需要阻止编译时重新排序,因此 GCC 的典型实现可能是asm("" ::: "memory").

执行任何操作的不是获取排序,而是阻止 GCC 假设另一个读取将读取相同内容的内存破坏。这不是 ISO C++std::atomic_thread_fence(std::memory_order_acquire)对非原子变量暗示的东西。(并且它总是暗示原子和易失性)。所以就像我说的,这可以在 GCC 中工作,但只能作为一个实现细节。


如果此内存曾被除 this an 以外的其他类型访问char*,或者如果底层内存被声明为char[]数组,则它也是严格别名 UB 。如果你得到了一个char*frommmap或其他东西,那么你很好。

除非offset已知为 4 的倍数,否则 UB 也可能未对齐。(尽管除非 GCC 选择 auto-vectorize,但这在 x86 上实际上不会咬你。)

您可以使用 GNU C 解决这两个问题,typedef uint32_t unaligned_u32 __attribute((may_alias, aligned(1)));但您仍然需要volatileatomic<T>循环阅读才能工作。


一般来说

std::atomic_thread_fence(std::memory_order_acquire);根据 C++ 内存模型的要求使用;这就是在编译时管理重新排序的原因。

编译x86时,不会变成任何asm指令;在 asm 中,这是一个空操作。但是如果你不告诉编译器它不能重新排序某些东西,你的代码可能会根据编译器优化级别而中断。

您可能很幸运,让编译器在原子加载后执行非原子mo_relaxed加载,或者如果您不告诉它不要,它可能会更早地执行非原子加载。