请注意,我不是在询问用其他方式(例如锁)替换 volatile
,我是在询问 volatile
的性质 - 因此我使用“needed” “从某种意义上说, volatile
或无 volatile
。
考虑这样一种情况,一个线程仅写入变量x
(Int32
),而另一个线程仅读取它。两种情况下的访问都是直接的。
需要 volatile
来避免缓存,对吗?
但是如果对 x
的访问是间接的——例如通过属性:
int x;
int access_x { get { return x; } set { x = value; } }
因此,两个线程现在仅使用 access_x
,而不是 x
。是否需要将x
标记为 volatile
?如果是,当不再需要 volatile
时是否存在一些间接限制?
更新:考虑读者的这样的代码(无写入):
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)
...
假设我为 x
跳过了 volatile
。将会发生什么/可能发生什么?
最佳答案
Is
x
needed to be marked asvolatile
?
是,也不是(但大部分是)。
"is",因为从技术上讲,您在这里没有任何保证。编译器(C# 和 JIT)可以进行他们认为合适的任何优化,只要优化不会改变在单线程中执行时代码的行为即可。一种明显的优化是省略对属性 setter 和 getter 的调用,而直接访问该字段(即内联)。当然,编译器可以做任何它想做的分析并进行进一步的优化。
“否”,因为实际上这通常不是问题。通过将访问封装在方法中,C# 编译器不会优化该字段,而 JIT 编译器不太可能会这样做(即使方法是内联的......同样,不能保证,但据我所知没有执行这样的优化,我认为 future 的版本不太可能会执行)。因此,剩下的就是内存一致性问题(使用 volatile
的另一个原因......即本质上处理在硬件级别执行的优化)。
只要您的代码仅在 Intel x86 兼容硬件上运行,该硬件就会将所有读取和写入视为 volatile 。
但是,其他平台可能有所不同。 Itanium 和 ARM 是两个具有不同内存模型的常见示例。
就我个人而言,我更喜欢写技术细节。尽管缺乏保证,但编写恰好可以工作的代码只是要求在未来某个时候该代码会因某种神秘的原因而停止工作。
所以恕我直言,您确实应该将该字段标记为 volatile
。
If yes is there some limit of indirection when volatile is not needed anymore?
没有。如果存在这样的限制,则必须将其记录下来才能有用。实际上,编译器优化方面的限制实际上只是“间接级别”(正如您所说)。但是任何间接级别都无法避免硬件级优化,甚至编译器优化方面的限制也是严格“在实践中”的。您无法保证编译器永远不会对任何任意深度的调用更深入地分析代码并执行更积极的优化。
更一般地说,我的经验法则是:如果我试图决定是否应该使用某些我知道通常用于防止并发相关场景中的错误的特定功能,我必须问“我真的需要这个吗?”功能,或者没有它代码也能正常工作吗?”,那么我可能对这个功能的工作方式了解不够,无法安全地避免使用它。
将其视为旧的“如果你必须问它要花多少钱,你就买不起。”的变体。
关于c# - 间接访问需要 volatile 吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39930867/