间接访问是否需要 volatile?
Is volatile needed with indirect access?
请注意,我不是在询问用其他方式(如锁定)替换 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
时是否存在一些间接限制?
Update:考虑这样的代码 reader(无写入):
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
。会发生什么/会发生什么?
Is x
needed to be marked as volatile
?
是,也不是(但大部分是)。
"Yes",因为从技术上讲,您在这里无法保证。允许编译器(C# 和 JIT)进行他们认为合适的 任何 优化,只要优化不会改变代码在单线程中执行时的行为。一个明显的优化是省略对 属性 setter 和 getter 的调用并直接访问该字段(即内联)。当然,允许编译器做任何它想做的分析并做进一步的优化。
"No",因为在实践中这通常不是问题。通过将访问包装在方法中,C# 编译器不会优化该字段,而 JIT 编译器 不太可能 这样做(即使方法是内联的......同样,不能保证,但是 AFAIK 没有执行这样的优化,我认为未来的版本不太可能)。所以剩下的就是内存一致性问题(使用 volatile
的另一个原因......即本质上处理在硬件级别执行的优化)。
只要您的代码只在 Intel x86 兼容硬件上运行 运行,该硬件就会将所有读取和写入视为易变的。
但是,其他平台可能会有所不同。 Itanium 和 ARM 是两个具有不同内存模型的常见示例。
就个人而言,我更喜欢写技术细节。尽管缺乏保证,编写恰好可以工作的代码只是要求在未来某个时间发现代码由于某种神秘原因而停止工作。
所以恕我直言,您确实应该将该字段标记为 volatile
。
If yes is there some limit of indirection when volatile is not needed anymore?
没有。如果存在这样的限制,则必须将其记录在案才能发挥作用。实际上,编译器优化方面的限制实际上只是 "level of indirection" (如您所说)。但是没有多少间接级别可以避免硬件级别的优化,甚至编译器优化方面的限制也严格 "in practice"。您无法保证编译器永远不会更深入地分析代码并执行更积极的优化,以进行任意深度的调用。
更一般地说,我的经验法则是:如果我试图决定是否应该使用一些我知道通常用于防止并发相关场景中的错误的特定功能,我必须问 "do I really need this feature, or will the code work fine without it?",那么我可能对该功能的工作方式了解不够,无法安全地避免使用它。
将其视为旧 "if you have to ask how much it costs, you can't afford it."
的变体
请注意,我不是在询问用其他方式(如锁定)替换 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
时是否存在一些间接限制?
Update:考虑这样的代码 reader(无写入):
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
。会发生什么/会发生什么?
Is
x
needed to be marked asvolatile
?
是,也不是(但大部分是)。
"Yes",因为从技术上讲,您在这里无法保证。允许编译器(C# 和 JIT)进行他们认为合适的 任何 优化,只要优化不会改变代码在单线程中执行时的行为。一个明显的优化是省略对 属性 setter 和 getter 的调用并直接访问该字段(即内联)。当然,允许编译器做任何它想做的分析并做进一步的优化。
"No",因为在实践中这通常不是问题。通过将访问包装在方法中,C# 编译器不会优化该字段,而 JIT 编译器 不太可能 这样做(即使方法是内联的......同样,不能保证,但是 AFAIK 没有执行这样的优化,我认为未来的版本不太可能)。所以剩下的就是内存一致性问题(使用 volatile
的另一个原因......即本质上处理在硬件级别执行的优化)。
只要您的代码只在 Intel x86 兼容硬件上运行 运行,该硬件就会将所有读取和写入视为易变的。
但是,其他平台可能会有所不同。 Itanium 和 ARM 是两个具有不同内存模型的常见示例。
就个人而言,我更喜欢写技术细节。尽管缺乏保证,编写恰好可以工作的代码只是要求在未来某个时间发现代码由于某种神秘原因而停止工作。
所以恕我直言,您确实应该将该字段标记为 volatile
。
If yes is there some limit of indirection when volatile is not needed anymore?
没有。如果存在这样的限制,则必须将其记录在案才能发挥作用。实际上,编译器优化方面的限制实际上只是 "level of indirection" (如您所说)。但是没有多少间接级别可以避免硬件级别的优化,甚至编译器优化方面的限制也严格 "in practice"。您无法保证编译器永远不会更深入地分析代码并执行更积极的优化,以进行任意深度的调用。
更一般地说,我的经验法则是:如果我试图决定是否应该使用一些我知道通常用于防止并发相关场景中的错误的特定功能,我必须问 "do I really need this feature, or will the code work fine without it?",那么我可能对该功能的工作方式了解不够,无法安全地避免使用它。
将其视为旧 "if you have to ask how much it costs, you can't afford it."
的变体