连锁且易失


77

我有一个用来表示状态的变量。可以从多个线程对其进行读写。

我正在使用Interlocked.Exchange并对其Interlocked.CompareExchange进行更改。但是我正在从多个线程中读取它。

我知道volatile可以用来确保变量不在本地缓存,但始终直接从内存中读取。

但是,如果我将变量设置为volatile,那么它将生成有关使用volatile并使用ref传递给Interlocked方法的警告。

我想确保每个线程都在读取变量的最新值,而不是某些缓存的版本,但是我不能使用volatile。

有一个,Interlocked.Read 但它适用于64位类型,并且在紧凑型框架上不可用。它的文档说,32位类型不需要它,因为它们已经在单个操作中执行过。

如果您使用互锁方法进行所有访问,则互联网上有一些声明不需要波动。但是,您无法使用Interlocked方法读取32位变量,因此无法对所有访问使用Interlocked方法。

有什么方法可以在不使用锁的情况下完成对变量的线程安全读取和写入操作?


2
实际上是个好问题。使用常规锁意味着关键的执行点,并确保您的值对于所有线程都是最新的。但是,Interlocked.Exchange并未使用实现lock,我找不到任何提供此类保证的引用。
Thorarin

Answers:


45

使用Interlocked.Xxx函数时,您可以放心地忽略该警告(请参阅此问题),因为它们总是执行易失性操作。因此,volatile对于共享状态,变量完全可以。如果您想不惜一切代价摆脱警告,实际上可以使用进行互锁的读取Interlocked.CompareExchange (ref counter, 0, 0)

编辑:实际上,当您要直接写入volatile状态变量时(即不使用),需要状态变量Interlocked.Xxx如jerryjvl所述,读取通过互锁(或易失性)操作更新的变量将使用最新值。


在哪里我应该用变量永远不会变成的值替换0?
流浪者

3
ew,如果没有,那么互锁将毫无用处。吓我一分钟:)
Thorarin

@Daniel:不,常量无关紧要,因为CompareExchange它将替换00if counteris 0—即无操作。
2009年

2
是否volatile保护非变量的读取免受编译器或JIT的优化?引用@jerryjvl的编辑是否不正确?
宾基

45

互锁操作和易失性不是真正应该同时使用的。收到警告的原因是(几乎?)它总是表示您对自己的操作有误解。

过度简化和措辞:
volatile表示每个读取操作都需要从内存中重新读取,因为可能会有其他线程更新该变量。当将其应用于可以由正在运行的体系结构自动读取/写入的字段时,这应该是您需要做的所有事情,除非您使用long / ulong,否则大多数其他类型都可以自动读取/写入。

当某个字段未标记为volatile时,您可以使用Interlocked操作做出类似的保证,因为这会导致刷新缓存,以便所有其他处理器都可以看到该更新……这具有将开销放在上面的好处。更新而不是读取。

这两种方法中哪种方法效果最好取决于您到底在做什么。而且,这种解释过于简单。但是从这一点应该清楚地知道,同时进行这两个步骤是没有意义的。


2
一起使用volatile和Interlocked并没有本质上的错误。收到警告的原因volatile是这volatile不是类型系统的一部分,并且收到引用的被调用者将不知道引用的变量需要易失性访问。
安东·泰克(09年

2
作为记录,这里有讨论,建议该答案是错误的。@jerryjvl,如果您想贡献一点,那就太好了。
罗曼·斯塔科夫

2
比起我第一次编写此答案时所意识到的,它涉及的更多微妙之处。我强烈建议您阅读Joe Duffy(和/或他的并发书),以在要解决的问题的背景下获得详细的了解。在这些事情上,他确实是权威。无锁的最大问题是,从某种意义上讲,无锁通常与性能有关,但是在此过程中仍然存在一些陷阱,这些陷阱可能会产生错误的结果或降低所寻求的性能。
jerryjvl 2011年

9
Joe Duffy在此博客文章中专门讨论了对volatile / Interlocked操作的引用。与引用互锁的混合是100%可以的,他请愿在C#编译器中添加一个特殊情况,以免在这种情况下发出警告。
Chuu 2011年

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.