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