Interlocked.CompareExchange是否使用内存屏障?

时间:2009-10-17 08:15:29

标签: c# multithreading optimization volatile memory-model

我正在阅读Joe Duffy关于Volatile reads and writes, and timeliness的帖子,我正试图了解帖子中最后一个代码示例的内容:

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不会使用内存屏障,那么整个锁获取过程将不公平,因为第一个获取锁的线程很可能会获得< strong>以下所有锁。我是否理解正确,或者我错过了什么?

编辑:主要问题是,在尝试读取m_state的值之前,调用CompareExchange是否会导致内存障碍。因此,当尝试再次调用CompareExchange时,是否所有线程都可以看到赋值0。

6 个答案:

答案 0 :(得分:23)

任何具有锁定前缀的x86指令都有完全内存屏障。如Abel的回答所示,Interlocked * API和CompareExchanges使用 lock - 前缀指令,例如lock cmpxchg。所以,它意味着记忆围栏。

是的,Interlocked.CompareExchange使用内存屏障。

为什么呢?因为x86处理器就这样做了。来自英特尔的Volume 3A: System Programming Guide Part 1,第7.1.2.2节:

  

对于P6系列处理器,锁定操作序列化所有未完成的加载和存储操作(即等待它们完成)。对于奔腾4和英特尔至强处理器,此规则也是如此,但有一个例外。

可能无法序列化引用弱有序内存类型(例如WC内存类型)的加载操作。

volatile与此讨论无关。这是关于原子操作的;为了支持CPU中的原子操作,x86保证完成所有先前的加载和存储。

答案 1 :(得分:10)

ref不遵守通常的volatile规则,尤其是在以下内容中:

volatile bool myField;
...
RunMethod(ref myField);
...
void RunMethod(ref bool isDone) {
    while(!isDone) {} // silly example
}

此处,RunMethod无法保证发现isDone的外部更改,即使基础字段(myField)为volatile; RunMethod不了解它,因此没有正确的代码。

然而!这应该是一个非问题:

  • 如果您使用Interlocked,则使用Interlocked 所有访问该字段
  • 如果您使用lock,则使用lock 所有访问该字段

遵循这些规则,它应该可以正常工作。


重新编辑;是的,行为Interlocked的关键部分。说实话,我不知道它是如何实现的(内存屏障等等 - 请注意它们是“InternalCall”方法,所以我无法检查;-p) - 但是:来自一个线程的更新将立即显示为所有其他只要他们使用Interlocked方法(因此我的观点)。

答案 2 :(得分:6)

似乎与Win32 API函数有相同的名称,但这个线程都是关于C#Interlocked类的。从它的描述来看,它保证其操作是原子的。我不确定这会如何转化为其他答案中提到的“完全记忆障碍”,但要自己判断。

在单处理器系统上,没有什么特别的事情发生,只有一条指令:

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

这里有一个有趣的读物,并且有一些错误的结论,但是关于这个主题的一切优秀是blog post on CompareExchange

答案 3 :(得分:2)

互锁功能可以保证在解析操作数时停止总线和CPU。直接后果是在你的cpu或其他程序中没有线程切换会在执行过程中中断互锁函数。

由于您正在传递对c#函数的引用,因此底层汇编程序代码将使用实际整数的地址,因此不会优化变量访问。它将完全按预期工作。

编辑:这是一个更好地解释asm指令行为的链接:http://faydoc.tripod.com/cpu/cmpxchg.htm
正如您所看到的,总线通过强制写入周期而停滞,因此任何其他尝试同时使用总线的“线程”(读取:其他cpu内核)将被置于等待队列中。

答案 4 :(得分:2)

MSDN说到Win32 API函数: “大多数互锁功能在所有Windows平台上都提供了完整的内存屏障

(例外是具有显式获取/释放语义的互锁函数)

由此我得出结论,C#运行时的Interlocked提供相同的保证,因为它们以其他相同的行为记录(并且它们解析为我知道的平台上的内部CPU语句)。不幸的是,由于MSDN倾向于提供样本而不是文档,因此没有明确说明。

答案 5 :(得分:0)

根据ECMA-335(第I.12.6.5节):

  

5。   显式原子操作。     类库提供各种原子操作   在里面   System.Threading.Interlocked    类。这些操作(例如,增量,   递减,交换和CompareExchange)执行隐式获取/释放   操作即可。

因此,这些操作遵循最不惊讶的原则