最差(实际上不会工作)
将的访问修饰符更改counter
为public 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上使用。
- 互锁的方法在执行的指令周围加上了完整的围栏,因此不会发生重新排序。
- 互锁方法不需要甚至不支持访问volatile字段,因为volatile在给定字段上的操作周围放置了半围墙,而联锁使用的是全围墙。
脚注:挥发物实际上是有益的。
由于volatile
不能防止此类多线程问题,它的用途是什么?一个很好的例子是说您有两个线程,一个线程总是写一个变量(例如queueLength
),而一个线程总是从同一个变量读取。
如果queueLength
不是易失性的,线程A可能会写入五次,但是线程B可能会认为这些写入被延迟(甚至可能以错误的顺序)。
解决方案是锁定,但在这种情况下也可以使用volatile。这样可以确保线程B始终可以看到线程A编写的最新内容。但是请注意,只有当您有从未读过的作家和从未写过的读者,并且您要写的东西是原子值时,此逻辑才起作用。一旦完成一次读-修改-写操作,就需要进入互锁操作或使用锁定。