【问题标题】:Example of C++ "Memory barrier" [duplicate]C ++“内存屏障”示例 [重复]
【发布时间】:2013-07-27 17:08:42
【问题描述】:

我正在阅读关于 volatile 关键字的这个问题的答案:

https://stackoverflow.com/a/2485177/997112

这个人说:

防止重新排序的解决方案是使用内存屏障, 这向编译器和 CPU 表明没有内存访问 可以在这一点上重新排序。在我们周围设置这样的障碍 易失性变量访问确保即使是非易失性访问也不会 在 volatile 中重新排序,允许我们编写线程安全的 代码。

但是,内存屏障也确保所有挂起的读/写 当到达障碍时执行,所以它有效地给了我们 我们自己需要的一切,使 volatile 变得不必要。我们只能 完全删除 volatile 限定符。

这个“内存屏障”是如何在 C++ 中实现的?

编辑:

谁能给出一个简单的代码示例?

【问题讨论】:

  • @HansPassant 在您链接到的问题中没有简单的 C++ 内存屏障示例
  • 谁保证会很简单?这是C++,应该很难。如果不是,那么任何人都可能是 C++ 程序员 :) 至少问题标题中的“内存屏障”一词应该暗示这是完全相同的问题。

标签: c++ multithreading volatile memory-barriers


【解决方案1】:

在 C++11 中使用内存屏障很简单:

std::atomic<int> i;

i的所有访问都将受到内存屏障的保护。

【讨论】:

  • 确实,还是推荐这个视频imo youtu.be/ZQFzMfHIxng ;)
  • ... 除非您特别不关心正在订购 wrt 的访问权限。对其他对象的其他操作,这种情况下你可以i.load(std::memory_order_relaxed)。 (默认为seq_cst,但通常acquirerelease 就足够了,而且价格便宜得多,尤其是对于x86 上的商店)
【解决方案2】:

这非常依赖于硬件。来自Linux内核相当长的documentation of memory barrier

The Linux kernel has eight basic CPU memory barriers:

TYPE                MANDATORY               SMP CONDITIONAL
===============     ======================= ===========================
GENERAL             mb()                    smp_mb()    
WRITE               wmb()                   smp_wmb()
READ                rmb()                   smp_rmb()   
DATA DEPENDENCY     read_barrier_depends()  smp_read_barrier_depends()

让我们特别选择其中之一:smp_mb()。 如果你打开asm/x86/um/asm/barrier.h,你会发现当CONFIG_SMP被定义时,

#define smp_mb()    mb()

而且如果你向上滚动,你可以看到根据平台的不同,mb 有不同的实现:

// on x86-32
#define mb()        alternative("lock; addl $0,0(%%esp)", "mfence", X86_FEATURE_XMM2)
// on other platforms
#define mb()        asm volatile("mfence" : : : "memory")

this thread 中讨论了有关这两件事之间差异的更多信息。我希望这会有所帮助。

【讨论】:

  • 这些是系统调用吗?所以程序员不能自己创建内存屏障?
  • 它们不是系统调用(顺便说一句,您可以通过运行man 2 syscalls 找到所有系统调用)。 c++ 编译器会将您的 mb() 调用替换为已编译代码中相应的 ASM 指令。你可以自己创建一个内存屏障,它包括调用一些汇编指令,就像 linux 源代码一样。
【解决方案3】:

通常,存在“内在函数”——这些是编译器对它们如何操作具有特殊知识的特殊函数(特别是它们是内存屏障)。名称因编译器而异(有时对于同一编译器的不同架构)。

例如,MSVC 使用_ReadBarrierWriteBarrier_ReadWriteBarrier

在 x86 中,它会生成 lfencesfencemfence 指令——分别执行“加载”、“存储”和“所有内存操作”屏障——换句话说,lfence将成为内存读取操作的屏障,sfence 将成为“内存写入”屏障,mfence 将成为读取和写入操作的屏障。

【讨论】:

  • 这实际上不是lfence 所做的;它唯一的内存排序效果是阻止对来自弱排序 WC 内存(例如视频 RAM)的 SSE4.1 movntdqa 加载进行重新排序,以防止随后的加载/存储重新排序。它还阻止后面指令的 OoO exec 开始,直到所有前面的指令都完成 执行 (但对于存储,不一定从存储缓冲区提交)。 x86 已经不允许对除 StoreLoad 之外的正常加载/存储进行内存重新排序,因此编译器可以通过不进行编译时重新排序来获得 LoadLoad 和 LoadStore 排序。
  • MSDN 链接已失效,但那些内在函数不会发出这些指令;似乎它们只阻止编译时重新排序。 (其中一些使编译器不进行常量传播,例如 GNU C asm("" ::: "memory"),因此它实际上会重新加载。)godbolt.org/z/Gneborsxf 没有显示 asm 指令,甚至对于 MSVC 19.28 -O2(x86 或 x64)中的 _ReadWriteBarrier 也不显示)。另请参阅 When should I use _mm_sfence _mm_lfence and _mm_mfence 了解这些指令的实际内在函数,以及为什么您通常只需要编译器屏障。
猜你喜欢
  • 1970-01-01
  • 2014-02-04
  • 1970-01-01
  • 1970-01-01
  • 2018-03-21
  • 1970-01-01
  • 2011-09-28
  • 2011-07-05
  • 1970-01-01
相关资源
最近更新 更多