std::x86 上需要memory_order_acquire围栏吗?

std::memory_order_acquire fence necessary on x86?

本文关键字:acquire order memory x86 std      更新时间:2023-10-16

鉴于 x86 具有强大的内存模型,是否需要std::memory_order_acquire围栏(非操作(?

例如,如果我有以下代码:

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
}

在返回声明之前我真的需要std::atomic_thread_fence(std::memory_order_acquire)吗?

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

还是有一些副作用使得需要获取围栏?

在x86 上是否需要采集围栏(而不是操作(?

欢迎任何意见。

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

您投射到非atomicvolatileuint32_t*和取消引用!!

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

  • https://electronics.stackexchange.com/questions/387181/mcu-programming-c-o2-optimization-breaks-while-loop/387478#387478
  • 多线程程序卡在优化模式下,但在 -O0 中正常运行
  • 何时将易失性与多线程一起使用?(永远不要将原子与mo_relaxed一起使用(

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中工作,但只能作为实现细节。


如果此内存使用char*以外的其他类型访问,或者如果底层内存被声明为char[]数组,它也是严格混叠 UB。 如果你从mmap或其他东西那里得到了char*,那么你就没事了。

也可能是UB错位,除非已知offset是4的倍数。 (尽管除非 GCC 选择自动矢量化,否则这在 x86 上不会在实践中咬你。

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


通常

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

当编译x86时,它不会变成任何asm指令;在asm中,它是一个无操作。 但是,如果您不告诉编译器它无法对某些内容进行重新排序,则代码可能会中断,具体取决于编译器优化级别。

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