有人可以提供一个简单的解释,说明如何使用Threading.MemoryBarrier在.Net中实现“完全栅栏”吗?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (12)

我很清楚MemoryBarrier的用法,但不是在运行时幕后发生的事情。任何人都可以对发生的事情给出一个好的解释吗?

提问于
用户回答回答于

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

需要内存隔离,因为当前的通用体系结构不提供强大的内存模型 - 例如x86 / x64可以重新排序与写入相关的读取。(更彻底的来源是“英特尔®64和IA-32架构软件开发人员手册”,8.2.2 P6和更新处理器系列中的内存排序)。作为来自gazillions的一个例子,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和其他体系结构都有自己的内存模型 - 从而保证内存排序(或缺少内存) - 以及他们自己的处理内存存储/加载排序的指令/方式。

用户回答回答于

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

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

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

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

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

在.NET Framework中,有很多方法可以发出栅栏:互锁,监视器,ReaderWriterLockSlim等。

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

扫码关注云+社区