简而言之,是否可以使用以下代码将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_release
和acquire
结合使用可获得相同的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_release
和mo_acquire
一起使用时得到的asm相同。
C++ How is release-and-acquire achieved on x86 only using MOV?解释了为什么x86 asm内存模型至少与acq_rel一样强。
asm语句中的sfence
和lfence
指令与完全无关,只需要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指令来刷新存储缓冲区,并等待其发生,然后再进行后续加载。又名完全障碍(例如mfence
或lock
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()
,就应该使用mfence
将std::atomic
留给编译器。
When to use volatile with multi threading?-通常从不;使用std::atomic
和mo_relaxed
代替volatile
。
关于c++ - 在c++中,我们能否通过volatile +内存栅栏(sfence + lfence)保证两个线程之间发生事前?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60669710/