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

Mic*_*lli 35 .net c# multithreading interlocked interlocked-increment

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

有些代码我不拥有,也无法更改为使用在几个不同线程中增加int计数器(numberOfUpdates)的锁.所有通话都使用:

Interlocked.Increment(ref numberOfUpdates);
Run Code Online (Sandbox Code Playgroud)

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

int localNumberOfUpdates = Interlocked.CompareExchange(ref numberOfUpdates, 0, 0);
Run Code Online (Sandbox Code Playgroud)

要么

int localNumberOfUpdates = Thread.VolatileRead(numberOfUpdates);
Run Code Online (Sandbox Code Playgroud)

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

Pat*_*sel 34

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

int localNumberOfUpdates = Interlocked.CompareExchange(ref numberOfUpdates, 0, 0);
Run Code Online (Sandbox Code Playgroud)

会给你你正在寻找的东西.正如其他人所说,互锁操作是原子的.因此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/

  • 实际上,我只是看了Interlocked.cs的BCL.看起来好像`Interlocked.Read` for longs*实际上是用`Interlocked.CompareExchange(ref location,0,0)`实现的.所以我觉得`int的Interlocked.CompareExchange(ref foo,0,0)`应该没问题.如果有一个int`Interlocked.Read`,那就是它的实现方式. (6认同)
  • 这是个好建议.关于易失性读取:易失性读取将为您提供最新的可用值**但是,C#语言无法保证所有线程*都会观察到所有易失性读写的*单一规范排序.请记住,"我刚读完最新值"和"一个变量的读取不会针对不同变量的写入重新排序"之间存在很大差异,但很多人隐含地假设它们是相同的; 他们绝对不是! (5认同)
  • 我同意您关于在任何地方都坚持使用一种同步原语的观点。我看过很多“真正”知道自己在做什么的人写的文章,他们说 volatile 的实际含义令人困惑。所以我倾向于坚持使用 Interlocked 进行所有读取。但我不喜欢我必须使用“Interlocked.CompareExchange(ref foo,0,0)”而不是“Interlocked.Read(ref foo)”来“伪造”它,这从代码中清楚地表明我所有的想做的就是读书。 (3认同)

Tim*_*lds 5

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

如果numberOfUpdates定义为volatile int numberOfUpdates;您不必执行此操作,因为它的所有读取都将是易失性读取.


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

Thread.VolatileRead文档:

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

Interlocked.CompareExchange文档:

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

就这些方法的陈述行为而言,Thread.VolatileRead显然更为合适.您不想与numberOfUpdates其他值进行比较,也不想替换其值.你想要读它的价值.


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

lock (state)
{
    state.numberOfUpdates++;
}
Run Code Online (Sandbox Code Playgroud)

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

int value;
lock (state)
{
    value = state.numberOfUpdates;
}
Run Code Online (Sandbox Code Playgroud)

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


Han*_*ant 5

两者都有效吗(无论优化、重新排序、缓存等如何,都可能提供最新的价值)?

不,你获得的值总是陈旧的。该值可能有多陈旧是完全无法预测的。绝大多数情况下,它会过时几纳秒,具体取决于您对值采取行动的速度。但没有合理的上限:

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

无论您对价值做什么,都需要考虑到这一点。不用说,这可能非常非常困难。实际例子很难找到。.NET Framework 是一大块饱经战火的代码。您可以从参考源中看到对 VolatileRead 用法的交叉引用。点击次数:0。


归档时间:

查看次数:

11929 次

最近记录:

10 年,9 月 前