17

我很清楚 MemoryBarrier 的用法,但不清楚运行时幕后发生的事情。谁能很好地解释发生了什么?

4

2 回答 2

16

在一个非常强大的内存模型中,发出栅栏指令是不必要的。所有内存访问都将按顺序执行,所有存储都将是全局可见的。

需要内存栅栏是因为当前的常见架构不提供强大的内存模型- 例如,x86/x64 可以相对于写入重新排序读取。(更详尽的来源是“英特尔® 64 和 IA-32 架构软件开发人员手册,8.2.2 P6 和更近期的处理器系列中的内存订购”)。举个例子, Dekker 的算法在没有栅栏的 x86/x64 上会失败。

即使 JIT 生成的机器代码在其中仔细放置了带有内存加载和存储的指令,如果 CPU 然后重新排序这些加载和存储,它的努力也是无用的——它可以这样做,只要为当前保持顺序一致性的错觉。上下文/线程。

冒着过度简化的风险:它可能有助于将指令流产生的负载和存储可视化为一群狂暴的野生动物。当它们穿过一座狭窄的桥(您的 CPU)时,您永远无法确定动物的顺序,因为它们中的一些会更慢,一些更快,一些超越,一些落后。如果在开始时 - 当您发出机器代码时 - 通过在它们之间放置无限长的栅栏将它们分成组,您至少可以确保 A 组在 B 组之前。

栅栏确保读取和写入的顺序。措辞并不准确,但是:

  • 存储栅栏“等待”所有未完成的存储(写入)操作完成,但不影响加载。
  • 加载栅栏“等待”所有未完成的加载(读取)操作完成,但不影响存储。
  • 一个完整的栅栏“等待”所有存储和加载操作完成。它的效果是,在“栅栏的另一边”(比栅栏晚)上的写入和加载之前执行栅栏之前的读取和写入。

JIT 发出的完整围栏取决于(CPU)架构和它提供的内存排序保证。由于 JIT 确切地知道它运行在什么架构上,它可以发出正确的指令。

在我的 x64 机器上,使用 .NET 4.0 RC,它恰好是一个lock or.

            int a = 0;
00000000  sub         rsp,28h 
            Thread.MemoryBarrier();
00000004  lock or     dword ptr [rsp],0 
            Console.WriteLine(a);
00000009  mov         ecx,1 
0000000e  call        FFFFFFFFEFB45AB0 
00000013  nop 
00000014  add         rsp,28h 
00000018  ret 

英特尔® 64 和 IA-32 架构软件开发人员手册第 8.1.2 章:

  • “...锁定操作序列化所有未完成的加载和存储操作(即等待它们完成)。” ... “锁定操作相对于所有其他内存操作和所有外部可见事件是原子的。只有取指和页表访问才能传递锁定指令。锁定指令可用于同步一个处理器写入和另一个处理器读取的数据。”

  • 内存排序指令解决了这一特定需求。MFENCE在上述情况下可以用作完整的屏障(至少在理论上 - 一方面,锁定操作可能更快,另一方面它可能导致不同的行为)。MFENCE及其朋友可以在第 8.2.5 章“加强或削弱内存排序模型”中找到。

还有更多方法可以序列化存储和加载,尽管它们不切实际或比上述方法慢:

  • 在第 8.3 章中,您可以找到完整的序列化指令,例如CPUID. 这些序列化指令流也是如此:“没有任何东西可以传递序列化指令,而序列化指令不能传递任何其他指令(读、写、取指令或 I/O)”。

  • 如果您将内存设置为强非缓存 (UC),它将为您提供强大的内存模型:不允许推测性或乱序访问,所有访问都将出现在总线上,因此无需发出指令。:) 当然,这会比平时慢一点。

...

所以这取决于。如果有一台具有强排序保证的计算机,JIT 可能不会发出任何内容。

IA64 和其他架构有自己的内存模型——因此保证了内存排序(或缺少它们)——以及他们自己的指令/方法来处理内存存储/加载排序。

于 2010-03-22T19:22:58.833 回答
4

在进行无锁并发编程时,应该关心程序指令的重新排序。

程序指令重新排序可以发生在几个阶段:

  1. C#/VB.NET/F# 编译器优化
  2. JIT 编译器优化
  3. CPU 优化。

内存栅栏是确保程序指令特定顺序的唯一方法。基本上,内存栅栏是一类使 CPU 强制执行排序约束的指令。内存栅栏可以分为三类:

  1. 加载栅栏 - 确保没有加载 CPU 指令穿过栅栏
  2. 存储栅栏 - 确保没有存储 CPU 指令穿过栅栏
  3. 完整的栅栏 - 确保没有加载或存储 CPU 指令穿过栅栏

在 .NET Framework 中有很多方法可以发出栅栏:Interlock、Monitor、ReaderWriterLockSlim 等。

Thread.MemoryBarrier 在 JIT 编译器和处理器级别发出一个完整的栅栏。

于 2010-03-22T17:49:58.247 回答