【问题标题】:Interlocked and volatile互锁且易变
【发布时间】:2010-11-14 06:20:34
【问题描述】:

我有一个变量用来表示状态。它可以从多个线程中读取和写入。

我正在使用Interlocked.ExchangeInterlocked.CompareExchange 来更改它。但是我正在从多个线程中读取它。

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

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

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

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

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

有没有什么方法可以在不使用锁的情况下完成我的变量的线程安全读写?

【问题讨论】:

  • 其实是个好问题。使用常规锁意味着一个关键执行点,并保证您的值对于所有线程都是最新的。但是,Interlocked.Exchange 没有使用lock 实现,我找不到任何参考说明它做出了这样的保证。

标签: c# .net mono


【解决方案1】:

实际上不应该同时使用互锁操作和易失性操作。您收到警告的原因是因为它(几乎?)总是表明您误解了自己在做什么。

过度简化和解释:
volatile 表示每次读取操作都需要从内存中重新读取,因为可能有其他线程在更新变量。当应用于可以由您运行的架构以原子方式读取/写入的字段时,这应该是您需要做的所有事情,除非您使用 long/ulong,大多数其他类型都可以原子方式读取/写入。

当一个字段没有被标记为 volatile 时,你可以使用Interlocked 操作来做出类似的保证,因为它会导致缓存被刷新,这样更新对所有其他处理器都是可见的……这有好处您将开销放在更新而不是读取上。

这两种方法中哪一种效果最好取决于您具体在做什么。这种解释过于简单化了。但是从这里应该清楚的是,同时做这两件事是没有意义的。

【讨论】:

  • 将 volatile 和 Interlocked 一起使用并没有本质上的错误。收到警告的原因是 volatilevolatile 不是类型系统的一部分,接收到引用的被调用者不会知道被引用的变量需要 volatile 访问。
  • 作为记录,有discussion over here建议这个答案是错误的。 @jerryjvl,如果你愿意贡献,那就太好了。
  • 其中涉及的微妙之处比我第一次写这个答案时所意识到的要多。我强烈建议阅读 Joe Duffy(和/或他的关于并发的书),以详细了解正在解决的问题。当涉及到这些问题时,他确实是权威。无锁的最大问题是它通常在某种意义上与性能有关,但在此过程中存在一些陷阱,可能会产生错误的结果或降低所寻求的性能。
  • Joe Duffy 在 this blog entry 中专门谈到了对 volatile / Interlocked 操作的引用。将联锁与引用混合是 100% 可以的,他请求在 C# 编译器中添加一个特殊情况,不要在这种情况下引发警告。
【解决方案2】:

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

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

【讨论】:

  • 我应该在哪里用我的变量永远不会出现的值替换 0?
  • 唷,如果没有,Interlocked 将毫无用处。吓了我一分钟:)
  • @Daniel: 不,常量无关紧要,因为如果counter0CompareExchange 将用0 替换0 — 即无操作。
  • 对非volatile 变量的读取是否受到保护,不会被编译器或 JIT 优化掉?引用@jerryjvl 的编辑不正确吗?
  • 假设x 是一个共享变量。在一个线程中,我使用a+=x; a+=x; 执行a 是线程本地的。在第二个线程中,我做Interlocked.Increment(ref x);。如果没有volatile,第一个线程可能会将x 放在一个寄存器中,并将它用于它的两个语句,而忽略增量。所以volatile 似乎是必要的,即使是Interlocked
猜你喜欢
  • 2010-09-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-06
  • 2018-05-23
  • 1970-01-01
  • 1970-01-01
  • 2010-09-16
相关资源
最近更新 更多