c++ - 在c++中,我们能否通过volatile +内存栅栏(sfence + lfence)保证两个线程之间发生事前?

标签 c++ x86 inline-assembly thread-synchronization memory-barriers

简而言之,是否可以使用以下代码将src中存储的数据正确复制到dst中?

volatile bool flag = false;

// In thread A.
memset(mid, src, size);
__asm__ __volatile__("sfence" ::: "memory");
flag = true;

// In thread B.
while (flag == false);
__asm__ __volatile__("lfence" ::: "memory");
memset(dst, mid, size);

最佳答案

https://gcc.gnu.org/wiki/DontUseInlineAsm

请勿在实践中使用此代码,将std::atomic<bool>memory_order_releaseacquire结合使用可获得相同的asm代码源(但无需不必要的防护和防护)

但是,对定义volatile行为的编译器来说,看起来是安全的,这样volatile bool flag上的数据争用UB就不成问题。对于可以编译Linux内核的GCC之类的编译器,就是这种情况(像您正在执行的那样,它使用volatile滚动其自己的原子)。

ISO C++并不严格要求这样做,例如,在没有一致性共享内存的计算机上可能存在一种假设的实现,因此原子存储将需要显式刷新。但是实际上没有任何这样的实现。 (不过,有些嵌入式系统中volatile存储使用不同或额外的指令来使MMIO正常工作。)

存储之前的障碍使其成为发布存储,而加载之后的障碍使其成为获取负载。 https://preshing.com/20120913/acquire-and-release-semantics/发生之前,只能通过获取负载看到的发布存储建立。

x86 asm内存模型已经禁止除StoreLoad之外的所有重新排序,因此仅编译时重新排序需要为块。除了那些效率低下的LFENCE和SFENCE指令外,这将编译为asm,与将std::atomic<bool>mo_releasemo_acquire一起使用时得到的asm相同。

C++ How is release-and-acquire achieved on x86 only using MOV?解释了为什么x86 asm内存模型至少与acq_rel一样强。

asm语句中的sfencelfence指令与完全无关,只需要asm("" ::: "memory")编译器障碍部分。 https://preshing.com/20120625/memory-ordering-at-compile-time/。编译时重新排序仅需遵守C++内存模型,但是x86内存模型将确定编译器选择的内容。 (程序顺序+具有存储转发功能的存储缓冲区=比acq_rel稍强)

(没有输出操作数的GNU C asm语句是隐式易失的,因此我省略了显式的volatile。)

(除非您试图同步NT存储库?否则,您只需要sfence,而不需要lfence。)
Does the Intel Memory Model make SFENCE and LFENCE redundant?是的。内部使用NT存储的内存集将使用sfence本身,以使其与x86上使用的standard C++ atomics / ordering -> asm mapping兼容。如果您使用其他映射(例如自由使用没有存储的NT存储),则理论上可以中断互斥锁关键部分,除非您也滚动自己的互斥锁。 (实际上,大多数互斥量实现在take和release中都使用lock ed指令,这是一个完整的障碍。)

由于x86的内存模型,带有内存缓冲区的空asm语句有点像atomic_thread_fence(std::memory_order_acquire_release)atomic_thread_fence(acq_rel)将编译为零个asm指令,仅阻止编译时重新排序。

只有seq_cst线程隔离栅需要发出任何asm指令来刷新存储缓冲区,并等待其发生,然后再进行后续加载。又名完全障碍(例如mfencelock ed指令,例如lock add qword ptr [rsp], 0)。

不要使用volatile和内联asm滚动自己的原子

是的,可以,我希望您只是想了解事物的工作原理。

由于使用lfence(乱序的执行障碍,对于内存排序基本上没有用),您最终制作出的效率远低于所需的效率,而不仅仅是编译器障碍。还有一个不必要的sfence

对于基本相同的问题,请参见When should I use _mm_sfence _mm_lfence and _mm_mfence,但使用内在函数而不是嵌入式asm。通常,您只需要在NT存储内部函数之后添加_mm_sfence(),就应该使用mfencestd::atomic留给编译器。

When to use volatile with multi threading?-通常从不;使用std::atomicmo_relaxed代替volatile

关于c++ - 在c++中,我们能否通过volatile +内存栅栏(sfence + lfence)保证两个线程之间发生事前?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60669710/

相关文章:

android - 优化 NEON 装配功能

c++ - Windows - 在 C++ 中读取鼠标的 dpi 设置

c++ - 删除动态分配的交错数组

c++ - 循环缓冲区的线程安全实现

assembly - 为什么我的数据部分在编译后的二进制文件中出现两次? Ubuntu、x86、nasm、gdb、reaelf

c - 汇编消息 : Error: attempt to move . org 向后

c++ - 类模板 - 从 "box"对象中删除项目

c# - 有没有办法动态生成 native x86 代码并从 .NET 执行它?

linux - 如何在程序集中创建具有动态指定文件路径的文件?

带有内联汇编的 C++,有什么问题吗?