我很清楚 MemoryBarrier 的用法,但不清楚运行时幕后发生的事情。任何人都可以对发生的事情做出很好的解释吗?
最佳答案
在真正强大的内存模型中,不需要发出栅栏指令。所有内存访问将按顺序执行,所有存储将全局可见。
需要内存栅栏,因为当前常见的架构 do not provide a strong memory model - 例如,x86/x64 可以相对于写入重新排序读取。 (更全面的来源是 "Intel® 64 and IA-32 Architectures Software Developer’s Manual ,8.2.2 P6 和更多最新处理器系列中的内存排序”)。作为来自无数的例子,Dekker's algorithm will fail on 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
Intel® 64 and IA-32 Architectures Software Developer’s Manual第 8.1.2 章:
...“锁定操作相对于所有其他内存操作和所有
外部可见事件。只有取指令和页表访问才能通过
锁定指令。锁定指令可用于同步由
一个处理器并由另一个处理器读取。”
MFENCE
在上述情况下可以用作完全屏障(至少在理论上 - 对于一个, locked operations might be faster ,对于两个是 might result in different behavior )。 MFENCE
和它的 friend 可以在第 8.2.5 章“加强或削弱内存排序模型”中找到。 还有更多的方法可以序列化存储和加载,但它们要么不切实际,要么比上述方法慢:
CPUID
.这些序列化指令流也是:“没有什么可以传递序列化指令和序列化指令不能通过任何其他指令(读、写、指令
取或 I/O)”。
...
所以这取决于。如果有一台具有强大排序保证的计算机,那么 JIT 可能不会发出任何信息。
IA64 和其他架构有自己的内存模型——因此保证内存排序(或没有它们)——以及他们自己的指令/方法来处理内存存储/加载排序。
关于.net - 有人可以简单解释一下如何使用 Threading.MemoryBarrier 在 .Net 中实现 'Full Fences' 吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2494057/