最差(实际上不起作用)
更改访问修饰符counter
to public volatile
正如其他人所提到的,这本身实际上根本不安全。要点是volatile
是在多个 CPU 上运行的多个线程可以并且将会缓存数据并重新排序指令。
如果是not volatile
,并且 CPU A 递增一个值,那么 CPU B 可能直到一段时间后才真正看到该递增的值,这可能会导致问题。
如果是volatile
,这只是确保两个CPU同时看到相同的数据。它根本不会阻止他们交错读取和写入操作,这是您试图避免的问题。
次好的:
lock(this.locker) this.counter++
;
这样做是安全的(只要您记得lock
您访问的其他任何地方this.counter
)。它防止任何其他线程执行由以下代码保护的任何其他代码locker
。
使用锁还可以防止上述多 CPU 重新排序问题,这很棒。
问题是,锁定速度很慢,如果你重复使用locker
在其他一些并不真正相关的地方,那么您最终可能会无缘无故地阻塞其他线程。
Best
Interlocked.Increment(ref this.counter);
这是安全的,因为它可以有效地“一次点击”完成读取、增量和写入,并且不能被中断。因此,它不会影响任何其他代码,并且您也不需要记住在其他地方锁定。它也非常快(正如 MSDN 所说,在现代 CPU 上,这通常实际上是一条 CPU 指令)。
I'm not entirely sure however if it gets around other CPUs reordering things, or if you also need to combine volatile with the increment.
联锁注释:
- 互锁方法在任意数量的内核或 CPU 上同时都是安全的。
- 互锁方法在它们执行的指令周围应用完整的栅栏,因此不会发生重新排序。
- 联锁方法不需要甚至不支持访问易失性字段,因为易失性在给定字段的操作周围放置了半栅栏,而互锁则使用完整栅栏。
脚注: 挥发物实际上有什么用处。
As volatile
不能防止此类多线程问题,它有什么用?一个很好的例子是,您有两个线程,其中一个总是写入变量(例如queueLength
),以及始终从同一个变量读取的变量。
If queueLength
不是易失性的,线程 A 可能会写入五次,但线程 B 可能会认为这些写入被延迟(甚至可能以错误的顺序)。
解决方案是锁定,但在这种情况下您也可以使用 volatile。这将确保线程 B 始终看到线程 A 写入的最新内容。但请注意,这个逻辑only如果你有从不读书的作家和从不写作的读者,and如果你正在写的东西是一个原子值。一旦执行了一次读-修改-写操作,您就需要进行互锁操作或使用锁。