volatile关键字的用法

3

我正在阅读关于volatile关键字的内容,并想知道下面这种情况会如何工作。

Class SomeClass
 {
  volatile int i = 10;
 }

两个线程尝试修改变量i。

Thread 1 does i = i + 1;
Thread 2 does i = i - 1;

操作步骤如下:
线程1将变量i读入CPU寄存器中。 此时进行线程切换。 线程2读取变量i并将其减1,因此现在i的值为9; 然后重新调度线程1,那么线程1会从寄存器中获取i的值(即10)并将其递增到11,然后写回i;还是它将从原始源中读取i值为9并将其递增到10?
谢谢

2
当你在阅读关于volatile的内容时,我建议你阅读Eric Lippert关于此的解释。简短的结论是:不要使用它。 - ken2k
2个回答

8

自增/自减操作不是原子操作,所以您描述的竞态条件是完全可能的。使用volatile无法防止这种情况发生。您需要使用lock或者可能是Interlocked.Increment来确保每个操作都是原子操作。


我也曾有同样的想法,但是在阅读了Eric Lippert的这篇博客文章之后(http://blogs.msdn.com/b/ericlippert/archive/2011/05/31/atomicity-volatility-and-immutability-are-different-part-three.aspx),我感到困惑。 - Sri Harsha Velicheti
@SriHarshaVelicheti 你有什么疑惑? - Servy
1
@SriHarshaVelicheti:那篇博客文章提到“每个易失性字段的读写也是原子的”,实际上,这是数据类型本身(int、char等)的属性。请注意,这并不意味着任何复合操作,比如(读取,增加1,写入)都是原子操作。它只是意味着读/写操作不会在中途被截断,但由于您给出的示例是复合操作,因此它们仍然不是线程安全的。 - voithos
这是博客中让我困惑的部分:“在C#中,“volatile”不仅意味着“确保编译器和JIT不对此变量执行任何代码重新排序或寄存器缓存优化”,还意味着“告诉处理器做他们需要做的事情,以确保我读取的是最新值,即使这意味着停止其他处理器并使它们将主内存与其缓存同步”。那么当线程1第一次被调度并尝试读取变量i时,它不是从原始源获取而不是CPU寄存器吗? - Sri Harsha Velicheti
@SriHarshaVelicheti 是的,它确实有用,但这并不能消除竞态条件。这意味着当你说,“线程1将变量i读入CPU寄存器。”时,该读取是在那个时间点读取的最新值。它并不能防止另一个线程在该线程进行更改并保存回来之前也读取该值并对其进行突变。volatile只是不能提供这种保证;lock可以。 - Servy

0

当你写道“线程1再次被调度,现在线程1会从寄存器中取出i的值(即10),将其增加到11并写回i,还是它会从原始源读取i的值为9,并将其增加到10?”- 编辑:在@Servy的帮助下,第一个线程将使用值10,因为它先前已经被读取以执行下一个加法操作。


1
不,实际上不会。它执行了易失性读取,并确保当它去获取从内存中更新的值时,该读取不会与某些写入重新排序。即使在写入读取值之后和对读取值进行某些操作之前写入值,该读取也不会被重新执行。给定程序简单地没有竞争条件。它需要一个“锁”来确保读/添加/写入被视为原子操作。 - Servy
@Servy 好的,但这会导致读取最实际的值,对吧? - lavrik
在读取逻辑发生的时候(这是在另一个线程进行写入之前),所以没有更新。你不能只将字段标记为 volatile,然后执行大量的非原子操作并使它们同步。只有 lock 可以为你完成这项工作。如果你想执行的所有操作都已经是原子性的,那么 volatile 才有用。但这个操作不是原子的。 - Servy
@Servy 对,但我不是在谈论同步一堆操作。我说的是“现在线程1会从寄存器中取i值”,以及“r会从原始源读取并将i值读取为9”。所以这是关于单个读取操作,而不是整个程序。我想这个单个读取操作将是原子性的,并且会获得最实际的值,或者即使这个语句是错误的? - lavrik
1
它确实进行了一次实际读取。当它第一次进行读取时,它确保获取的是真实值,而不是缓存值,并且该值没有被缓存在另一个CPU的缓存中。在完成该读取并将结果存储在其寄存器中之后,它不会因为其他线程更新了该值就返回内存并再次读取它。代码只有一次读取,执行了一次读取。如果另一个线程在读取变量后但在写回值之前修改了该变量,则没关系。volatile不能防止这种情况,你(错误地)声称它可以。 - Servy
就是这样!感谢@Servy提供的“它确实进行了实际读取”的提示。我错过了那个 :) - lavrik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接