读取由Interlocked在其他线程上更新的int

(这是重复: 如何正确读取Interlocked.Increment’ed int字段?但是,在阅读了答案和评论后,我仍然不确定正确的答案。)

有一些我不拥有的代码,并且无法更改为使用在几个不同线程中增加int计数器(numberOfUpdates)的锁。 所有来电均使用:

Interlocked.Increment(ref numberOfUpdates); 

我想在我的代码中读取numberOfUpdates。 既然这是一个int,我知道它不会撕裂。 但是,确保我获得最新价值的最佳方法是什么? 看起来我的选择是:

 int localNumberOfUpdates = Interlocked.CompareExchange(ref numberOfUpdates, 0, 0); 

要么

 int localNumberOfUpdates = Thread.VolatileRead(numberOfUpdates); 

两者都可以工作(无论优化,重新排序,缓存等,都可以提供最新的价值)? 一个优先于另一个? 还有第三种选择更好吗?

我坚信,如果您使用互锁来增加共享数据,那么您应该在访问共享数据的任何地方使用互锁。 同样,如果您在此处使用插入您喜欢的同步原语来增加共享数据,那么您应该访问该共享数据的任何位置使用插入您喜欢的同步原语

 int localNumberOfUpdates = Interlocked.CompareExchange(ref numberOfUpdates, 0, 0); 

会给你你正在寻找的东西。 正如其他人所说,互锁操作是primefaces的。 因此,Interlocked.CompareExchange将始终返回最新值。 我一直用它来访问像计数器这样的简单共享数据。

我对Thread.VolatileRead并不熟悉,但我怀疑它也会返回最近的值。 如果只是为了保持一致,我会坚持使用互锁方法。


附加信息:

我建议你看一下Jon Skeet的答案,为什么你可能想要回避Thread.VolatileRead(): Thread.VolatileRead Implementation

Eric Lippert在他的博客http://blogs.msdn.com/b/ericlippert/archive/2011/06/16/atomicity-volatility-and-immutability-are-different上讨论了C#内存模型的波动性和保证。 -part-three.aspx 。 直接从马口:“我不会尝试编写任何低锁代码,除了互锁操作最琐碎的用法。我将”易变“的用法留给真正的专家。”

我同意Hans的意见,即该值至少会持续几秒,但是如果你有一个不可接受的用例,它可能不太适合垃圾收集语言,如C#或非实时 – 时间OS。 Joe Duffy在这里有一篇关于互锁方法及时性的文章: http : //joeduffyblog.com/2008/06/13/volatile-reads-and-writes-and-timeliness/

Thread.VolatileRead(numberOfUpdates)就是你想要的。 numberOfUpdates是一个Int32 ,因此默认情况下你已经具有primefaces性,而Thread.VolatileRead将确保处理波动性。

如果numberOfUpdates被定义为volatile int numberOfUpdates; 你不必这样做,因为它的所有读取都已经是易失性读取。


关于Interlocked.CompareExchange是否更合适似乎存在混淆。 请考虑以下两个文档摘录。

Thread.VolatileRead文档:

读取字段的值。 该值是计算机中任何处理器编写的最新值,无论处理器数量或处理器缓存状态如何。

Interlocked.CompareExchange文档:

比较两个32位有符号整数是否相等,如果它们相等,则替换其中一个值。

就这些方法的陈述行为而言, Thread.VolatileRead显然更合适。 您不希望将numberOfUpdates与另一个值进行比较,并且您不希望替换其值。 你想读它的价值。


Lasse在他的评论中提出了一个很好的观点:你可能最好使用简单的锁定。 当其他代码想要更新numberOfUpdates它会执行以下操作。

 lock (state) { state.numberOfUpdates++; } 

当您想要阅读它时,您会执行以下操作。

 int value; lock (state) { value = state.numberOfUpdates; } 

这将确保您对primefaces性和波动性的要求,而无需深入研究更加模糊,相对较低级别的multithreading原语。

两者都可以工作(无论优化,重新排序,缓存等,都可以提供最新的价值)?

不,你得到的价值总是陈旧的。 价值如何陈旧是完全不可预测的。 绝大多数时间它会陈旧几纳秒,给予或接受,这取决于你对价值采取多快的行动。 但是没有合理的上限:

  • 当您的线程将其他线程切换到核心时,您的线程可能会丢失处理器。 典型的延迟大约是45毫秒,没有坚固的上限。 这并不意味着您的进程中的另一个线程也会被切换出来,它可以保持驱动并继续改变该值。
  • 就像任何用户模式代码一样,您的代码也会受到页面错误的影响。 当处理器需要RAM用于另一个进程时发生。 在负载很重的机器上,可以并且将页面输出活动代码。 有时会出现鼠标驱动程序代码,例如留下冻结的鼠标光标。
  • 托管线程受到近乎随机的垃圾收集暂停的影响。 倾向于成为较小的问题,因为可能还会暂停另一个改变该值的线程。

无论您如何处理价值,都需要考虑到这一点。 毋庸置疑,这可能非常非常困难。 实际的例子很难得到。 .NET Framework是一大堆战争伤痕累累的代码。 您可以从参考源中看到对VolatileRead的使用的交叉引用。 点击次数:0。

好吧,正如Hans Passant所说,你读到的任何价值都会有些陈旧。 您只能控制其他共享值与您刚读过的共享值保持一致的保证,在代码读取几个没有锁的共享值的情况下使用内存栅栏(即:处于相同程度的“陈旧性”)

Fences还具有破坏某些编译器优化和重新排序的效果,从而防止在不同平台上的发布模式中出现意外行为。

Thread.VolatileRead将导致发出一个完整的内存栅栏,因此在读取int时(在读取它的方法中)不会重新排序读取或写入。 显然,如果你只是阅读一个共享的价值(并且你没有阅读其他共享的东西,并且它们的顺序和一致性都很重要),那么它似乎没有必要……

但是我认为无论如何你都需要它来打败编译器或CPU的一些优化,这样你就不会让读取比必要的更“陈旧”。

虚拟Interlocked.CompareExchange将执行与Thread.VolatileRead(完全栅栏和优化失败行为)相同的操作。

在CancellationTokenSource使用的框架中有一个模式http://referencesource.microsoft.com/#mscorlib/system/threading/CancellationTokenSource.cs#64

 //m_state uses the pattern "volatile int32 reads, with cmpxch writes" which is safe for updates and cannot suffer torn reads. private volatile int m_state; public bool IsCancellationRequested { get { return m_state >= NOTIFYING; } } // .... if (Interlocked.CompareExchange(ref m_state, NOTIFYING, NOT_CANCELED) == NOT_CANCELED) { } // .... 

volatile关键字具有发出“半”栅栏的效果。 (即:它在读取之前阻止读取/写入被移动,并且在写入之后阻止读取/写入被移动)。