CSharp - Volatile vs. Interlocked vs. lock

  显示原文与译文双语对照的内容

假设一个类有一个由多个线程访问的public int counter 字段。 这里 int 仅递增或者递减。

增加该字段的使用方式,以及为什么?

  • lock(this.locker) this.counter++;
  • Interlocked.Increment(ref this.counter); ,
  • counter的访问修饰符更改为 public volatile

发现 volatile 之后,我已经删除了许多 lock 语句和 Interlocked的使用。 但是为什么不这样做是有原因的?

时间:

最差( 实际上不会工作)

counter的访问修饰符更改为 public volatile

就像其他人提到的,它本身是不安全的。 volatile的要点是多个运行在多个cpu上的线程可以缓存数据和re-order指令。

如果是不是volatile 和cpu增量值,那么cpub可能并没有真的看到递增,直到一段时间后,这可能会导致问题。

如果是 volatile,这将确保 2同时查看cpu的相同数据。 它根本不会阻止它们的读写操作,这就是你试图避免的问题。

第二个最好的:


lock(this.locker) this.counter++;

这对( 只要你记得 lockthis.counter 其他地方你访问) 来说是安全的。 它阻止任何其他线程执行由 locker 保护的任何其他代码。 使用锁也可以防止multi-cpu重新排序问题,这非常重要。

问题是,锁定速度很慢,如果你在其他地方re-use的locker 不相关,那么你就可以在没有任何原因的情况下阻塞其它线程。

最佳


Interlocked.Increment(ref this.counter);

这是安全的,因为它可以在'一个命中'中有效地读取,增量和写入,而不能被中断。 因此,它不会影响任何其他代码,也不需要在别处锁定。 它也是非常快速的( 就像MSDN所说,在现代的CPU上通常是一个CPU指令) 。

我还不完全确定它是否能绕过其他的cpu重新排序,或者你是否需要将volatile与增量结合起来。

脚注:什么volatile实际上适合。

由于 volatile 不防止此类多线程问题,它是什么? 一个很好的例子是你有 2个线程,一个总是写入一个变量( 说 queueLength ),另一个总是从同一个变量读取。

如果 queueLength 不是可变的,线程A 可能会写入 5次,但是线程B 可能会看到那些写为延迟的( 甚至可能以错误的顺序出现) 。

解决方案是锁定,但在这种情况下,你也可以使用 volatile 。 这将确保线程B 总是能看到线程A 所写的最新内容。 但请注意,该逻辑只 作品如果你有作家谁从来不读,但受众而写,和如果正在写的东西有是一个原子值。 只要你做了一个 read-modify-write,你就需要去做连锁操作或者使用锁。

编辑:评论中提到的,这些天我很高兴使用 Interlocked 单一变量的情况下显然好。 当它变得更加复杂时,我仍然会恢复锁定。。

当你需要增量时,使用 volatile 将于事无补- 因为读和写是单独的指令。 另一个线程可以在你读完之后再写之前更改该值。

我个人几乎总是锁——在某种程度上更容易得到正确的显然比波动或 Interlocked.Increment 权利。 就我而言,lock-free multi-threading是针对真正的线程专家,而我不是其中之一。 如果乔达菲和他的团队构建好库将parallelise事情没有尽可能多的锁我将建造的东西,这是令人难以置信的,我将用它的心跳,但当我做线程自己,我尽量保持简单。

" volatile"不替换 Interlocked.Increment ! 它只是确保变量没有被缓存,而是直接使用。

递增一个变量需要三个操作:

  1. 已读
  2. 增量
  3. 写入

Interlocked.Increment 将所有三个部件作为单个原子操作执行。

我做了一些测试,看看理论是如何工作的: kennethxu.blogspot.com/2009/05/interlocked-vs-monitor-performance.html 。我的测试更侧重于 CompareExchnage,但增量的结果类似。 在multi-cpu环境中不需要快速联锁。 这是 2年前 16个CPU服务器的增量测试结果。 请记住,测试在增加后也涉及安全阅读,这在现实中是典型的。


D:>InterlockVsMonitor.exe 16
Using 16 threads:
 InterlockAtomic.RunIncrement (ns): 8355 Average, 8302 Minimal, 8409 Maxmial
 MonitorVolatileAtomic.RunIncrement (ns): 7077 Average, 6843 Minimal, 7243 Maxmial

D:>InterlockVsMonitor.exe 4
Using 4 threads:
 InterlockAtomic.RunIncrement (ns): 4319 Average, 4319 Minimal, 4321 Maxmial
 MonitorVolatileAtomic.RunIncrement (ns): 933 Average, 802 Minimal, 1018 Maxmial

...