最差(实际上不起作用)
将访问修饰符更改为counter
topublic volatile
正如其他人所提到的,这本身并不安全。关键volatile
是在多个 CPU 上运行的多个线程可以并且将缓存数据并重新排序指令。
如果不是 volatile
,并且 CPU A 增加一个值,那么 CPU B 可能直到一段时间后才能真正看到该增加的值,这可能会导致问题。
如果是volatile
,这只是确保两个 CPU 同时看到相同的数据。它根本不会阻止他们交错读取和写入操作,这是您要避免的问题。
次好的:
lock(this.locker) this.counter++
;
这样做是安全的(只要您记得lock
您访问的其他任何地方this.counter
)。它防止任何其他线程执行任何其他由locker
. 使用锁也可以防止如上所述的多 CPU 重新排序问题,这很棒。
问题是,锁定很慢,如果你在其他一些不相关的地方重新使用,locker
那么你最终可能会无缘无故地阻塞你的其他线程。
最好的
Interlocked.Increment(ref this.counter);
这是安全的,因为它有效地执行了不可中断的“一击”读取、递增和写入操作。正因为如此,它不会影响任何其他代码,你也不需要记住在其他地方锁定。它也非常快(正如 MSDN 所说,在现代 CPU 上,这通常实际上是一条 CPU 指令)。
但是,我不完全确定它是否可以绕过其他 CPU 重新排序,或者您是否还需要将 volatile 与增量相结合。
联锁注意事项:
- 互锁方法在任何数量的内核或 CPU 上都是安全的。
- 互锁方法在它们执行的指令周围应用了一个完整的栅栏,因此不会发生重新排序。
- Interlocked 方法不需要甚至不支持对 volatile 字段的访问,因为 volatile 在给定字段上的操作周围放置了半栅栏,而 interlocked 则使用完整栅栏。
脚注: volatile 实际上有什么好处。
由于volatile
不能防止这些类型的多线程问题,它有什么用?一个很好的例子是说你有两个线程,一个总是写入一个变量(比如queueLength
),一个总是从同一个变量中读取。
如果queueLength
不是 volatile,线程 A 可能会写入五次,但线程 B 可能会将这些写入视为延迟(甚至可能以错误的顺序)。
一种解决方案是锁定,但在这种情况下您也可以使用 volatile。这将确保线程 B 将始终看到线程 A 编写的最新内容。但是请注意,此逻辑仅适用于您有从不阅读的作者和从不写作的读者,并且您正在编写的内容是原子值的情况。一旦您执行了一次读取-修改-写入,您就需要进行互锁操作或使用锁。