假设一个类具有一个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
将的访问修饰符更改counter为public 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不是易失性的,线程A可能会写入五次,但是线程B可能会认为这些写入被延迟(甚至可能以错误的顺序)。
解决方案是锁定,但在这种情况下也可以使用volatile。这样可以确保线程B始终可以看到线程A编写的最新内容。但是请注意, 只有 当您有从未读过的作家和从未写过的读者, 并且 您要写的东西是原子值时,此逻辑 才 起作用。一旦完成一次读-修改- 写操作,就需要进入互锁操作或使用锁定。