间接访问时需要的易失性

本文关键字:易失性 访问 | 更新日期: 2023-09-27 18:12:04

请注意,我不是在问用其他方法(如锁)替换volatile,我是在问volatile的性质-因此我在意义上使用"需要",volatile或没有volatile

考虑这样一种情况,一个线程只写变量x (Int32),而另一个线程只读它。这两种情况下的访问都是直接的。

需要volatile来避免缓存,对吗?

但是如果对x的访问是间接的呢?例如通过property:
int x;
int access_x { get { return x; } set { x = value; } } 

所以两个线程现在只使用access_x,而不是xx是否需要标记为volatile ?如果是,当不再需要volatile时,是否有一些间接限制?

Update:考虑读取器的代码(没有写入):

if (x>10)
   ...
// second thread changes `x`
if (x>10)
  ...

在第二个if编译器可以计算使用旧值,因为x可以在缓存中,没有volatile就不需要重新获取。我的问题是关于这样的变化:

if (access_x>10)
   ...   
// second thread changes `x`
if (access_x>10)
  ...

假设我跳过volatile而选择x。会发生什么/会发生什么?

间接访问时需要的易失性

是否需要将x标记为volatile ?

是,也不是(但大多数是)。

"是的",因为从技术上讲,这里没有保证。编译器(c#和JIT)被允许对进行他们认为合适的任何优化,只要该优化不会改变代码在单线程中执行时的行为。一个明显的优化是省略对属性setter和getter的调用,直接访问字段(即内联)。当然,编译器可以做任何它想做的分析和进一步的优化。

"No",因为在实践中这通常不是问题。使用方法封装的访问,c#编译器不会优化字段,JIT编译器不可能这样做(即使方法是内联的…再次,不保证,但我敢肯定,这样的优化没有执行,我认为未来的版本不太可能会)。所以你剩下的就是内存一致性问题(使用volatile的另一个原因)。本质上是处理在硬件级别执行的优化)。

只要你的代码只在Intel x86兼容的硬件上运行,该硬件就会将所有的读写操作视为易失性的。

然而,其他平台可能有所不同。安腾和ARM是两个常见的例子,它们有不同的内存模型。

就我个人而言,我更喜欢写技术细节。尽管缺乏保证,但编写恰好可以工作的代码只是要求在未来的某个时间,代码由于某种神秘的原因停止工作。

所以我认为你真的应该把这个字段标记为volatile

如果是,当不再需要volatile时是否有间接限制?

。如果存在这样的限制,则必须将其记录下来才能发挥作用。在实践中,编译器优化方面的限制实际上只是一个"间接级别"(如您所说)。但是没有多少间接级别可以避免硬件级别的优化,甚至在编译器优化方面的限制也是严格的"在实践中"。你不能保证编译器永远不会更深入地分析代码并执行更激进的优化,对任何任意深度的调用。


更一般地说,我的经验法则是:如果我试图决定是否应该使用一些我知道通常用于防止并发相关场景中的错误的特定功能,并且我必须问"我真的需要这个功能吗?或者没有它代码会正常工作吗?",那么我可能对该功能的工作方式了解不够,无法安全地避免使用它。

把这句话看作是"如果你要问它值多少钱,那你就买不起。"