Interlocked.CompareExchange是否使用内存屏障?
我正在阅读Joe Duffy关于Volatile读取和写入以及及时性的post,我正在尝试理解post中最后一个代码示例:
while (Interlocked.CompareExchange(ref m_state, 1, 0) != 0) ; m_state = 0; while (Interlocked.CompareExchange(ref m_state, 1, 0) != 0) ; m_state = 0; …
执行第二次CMPXCHG操作时,它是否使用内存屏障来确保m_state的值确实是写入它的最新值? 或者它只是使用已存储在处理器缓存中的某些值? (假设m_state未声明为volatile)。
如果我理解正确,如果CMPXCHG不会使用内存屏障,那么整个锁获取过程将不公平,因为第一个获取锁的线程很可能是将获得所有锁的线程。 以下锁 。 我理解正确,还是我错过了什么?
编辑 :主要问题实际上,在尝试读取m_state的值之前,调用CompareExchange是否会导致内存障碍。 因此,当尝试再次调用CompareExchange时,是否所有线程都可以看到赋值0。
任何具有锁定前缀的x86指令都具有完整的内存屏障 。 如Abel所示,Interlocked * API和CompareExchanges使用lock -prefixed指令,例如lock cmpxchg
。 所以,它意味着记忆围栏。
是的,Interlocked.CompareExchange使用内存屏障。
为什么? 因为x86处理器就这样做了。 从英特尔的第3A卷:系统编程指南第1部分 ,第7.1.2.2节:
对于P6系列处理器, 锁定操作会序列化所有未完成的加载和存储操作 (即等待它们完成)。 Pentium 4和Intel Xeon处理器也适用这一规则,但有一个例外。 引用弱有序内存类型(例如WC内存类型)的加载操作可能无法序列化。
volatile
与此讨论无关。 这是关于primefaces操作的; 为了支持CPU中的primefaces操作,x86保证完成所有先前的加载和存储。
ref
不遵守通常的volatile
规则,特别是在以下方面:
volatile bool myField; ... RunMethod(ref myField); ... void RunMethod(ref bool isDone) { while(!isDone) {} // silly example }
在这里,即使基础字段( myField
)是volatile
,也不保证RunMethod
能够发现对isDone
外部更改; RunMethod
不知道它,所以没有正确的代码。
然而! 这应该是一个非问题:
- 如果您使用的是
Interlocked
,则使用Interlocked
对该字段进行所有访问 - 如果您使用的是
lock
,则使用lock
来访问该字段
遵循这些规则,它应该工作正常。
重新编辑; 是的,这种行为是Interlocked
的关键部分。 说实话,我不知道它是如何实现的(内存屏障等等 – 请注意它们是“InternalCall”方法,所以我无法检查;-p) – 但是:来自一个线程的更新将立即显示为所有其他人只要他们使用Interlocked
方法(因此我的观点)。
似乎与Win32 API函数有相同的名称进行了一些比较,但这个线程都是关于C# Interlocked
类的。 从它的描述,它保证其操作是primefaces的。 我不确定这会如何转化为其他答案中提到的“完全记忆障碍”,但要自己判断。
在单处理器系统上,没有什么特别的事情发生,只有一条指令:
FASTCALL_FUNC CompareExchangeUP,12 _ASSERT_ALIGNED_4_X86 ecx mov eax, [esp+4] ; Comparand cmpxchg [ecx], edx retn 4 ; result in EAX FASTCALL_ENDFUNC CompareExchangeUP
但在多处理器系统上,硬件锁用于防止其他内核同时访问数据:
FASTCALL_FUNC CompareExchangeMP,12 _ASSERT_ALIGNED_4_X86 ecx mov eax, [esp+4] ; Comparand lock cmpxchg [ecx], edx retn 4 ; result in EAX FASTCALL_ENDFUNC CompareExchangeMP
这里有一个有趣的读物,并且有一些错误的结论,但是这个主题的优秀是这篇关于CompareExchange的博客文章 。
互锁function可以保证在解析操作数时停止总线和CPU。 直接后果是在您的cpu或其他程序上没有线程切换将在执行过程中中断互锁function。
由于您正在传递对c#函数的引用,因此底层汇编程序代码将使用实际整数的地址,因此不会优化变量访问。 它将完全按预期工作。
编辑:这是一个更好地解释asm指令行为的链接: http : //faydoc.tripod.com/cpu/cmpxchg.htm
正如您所看到的,总线通过强制写入周期而停止,因此任何其他尝试同时使用总线的“线程”(读取:其他CPU内核)将被置于等待队列中。
MSDN谈到了Win32 API函数:“ 大多数互锁函数在所有Windows平台上都提供了完整的内存屏障 ”
(exception是具有显式获取/释放语义的互锁函数)
由此我得出结论,C#运行时的Interlocked提供了相同的保证,因为它们被记录为具有其他相同的行为(并且它们解析为我知道的平台上的内部CPU语句)。 不幸的是,由于MSDN倾向于提供样本而不是文档,因此没有明确说明。
根据ECMA-335(第I.12.6.5节):
5.明确的primefaces操作。 类库在System.Threading.Interlocked类中提供各种primefaces操作。 这些操作(例如,Increment,Decrement,Exchange和CompareExchange) 执行隐式获取/释放操作 。
因此,这些操作遵循最不惊讶的原则 。