我有一个类 NonVolatileTest :
public class NonVolatileTest
{
public bool _loop = true;
}
我有两个代码示例:
1:
private static void Main(string[] args)
{
NonVolatileTest t = new NonVolatileTest();
Task.Run(() => { t._loop = false; });
while (t._loop) ;
Console.WriteLine("terminated");
Console.ReadLine();
}
2:
private static void Main(string[] args)
{
NonVolatileTest t = new NonVolatileTest();
Task.Run(() => { t._loop = false; });
Task.Run(() =>
{
while (t._loop) ;
Console.WriteLine("terminated");
});
Console.ReadLine();
}
在第一个示例中,所有工作都按预期进行,并且“while”循环永远不会终止,但在第二个示例中,所有工作据称“_loop”字段是易变的。
为什么?
附言。 VS 2013,.NET 4.5,x64 Release模式 & Ctrl + F5
假设:
这个“错误”可能与 TaskScheduler 有关。我认为,在JIT 将第二个任务编译运行之前,第一个任务已经完成,所以JIT 将更改后的值取走。
最佳答案
根据C# 5 specification (在带注释的 C# 4 规范中可以找到相同的段落),在第 10.5.3 节 - volatile 字段下,声明如下:
When a field-declaration includes a volatile modifier, the fields introduced by that declaration are volatile fields. For non-volatile fields, optimization techniques that reorder instructions can lead to unexpected and unpredictable results in multi-threaded programs that access fields without synchronization such as that provided by the lock-statement (§8.12). These optimizations can be performed by the compiler, by the run-time system, or by hardware. For volatile fields, such reordering optimizations are restricted:
(我的重点)
所以这被记录为不可预测的(也就是你无法控制的)。
这两段代码行为不同的事实可以归结为将代码提升到生成对象(用于闭包)的方法和不提升它之间的区别。
我的通灵密码阅读眼告诉我,这可能是第一种情况:
- 任务启动,但在调用委托(delegate)中的实际代码之前会产生开销
- 在调用委托(delegate)之前,主程序继续并设法启动循环,对控制变量进行一次读取,并继续重用其缓存的副本。
- 委托(delegate)最终会被执行,但这对循环没有影响,因为它已经读取了一次变量并且不想再次读取。
在第二种情况下,第一种情况有效地“通过一些对象引用读取变量”而第二种情况有效地“通过this
读取变量”这一事实稍微改变了上述情况> 引用”,可能会产生差异。
但真正的答案是您容易受到优化器的影响并且编写了不可预测的代码。
不要担心结果也是不可预测的。
对代码进行看似无关的微小更改可能会使优化器以不同的方式执行操作。
关于c# - 内存模型和线程池,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33280880/