我有一个类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’字段是易失性的。
为什么?
PS。VS 2013、.NET 4.5、x64发布模式& Ctrl + F5
假设:
此“错误”可能与TaskScheduler有关。我认为,在JIT将第二个任务放在编译和运行之前,第一个任务已经完成,所以JIT采用更改后的值。
发布于 2015-10-22 20:45:12
根据C# 5 specification (可以在带注释的C# 4规范中找到相同的段落),在第10.5.3节-易失性字段下,声明:
当字段声明包含volatile修饰符时,该声明引入的字段是volatile字段。对于非易失性字段,重新排序指令的优化技术可能会在多线程程序中导致意外和不可预测的结果,这些多线程程序在没有同步锁的情况下访问字段,例如语句提供的锁(§8.12)。这些优化可以由编译器、运行时系统或硬件执行。对于易失性字段,这样的重新排序优化受到限制:
(我的重点)
因此,这被证明是不可预测的(也就是你无法控制的)。
这两段代码行为不同的事实可以归结为将代码提升到生成的对象(用于闭包)上的方法和不提升它之间的差异。
我的通灵代码阅读眼睛告诉我,这可能是在第一种情况下发生的事情:
在第二种情况下,上述场景略有不同,因为第一种场景有效地“通过某些对象引用读取变量”,而第二种场景有效地“通过this引用读取变量”,这可能会造成差异。
但这里真正的答案是,您倾向于使用优化器,并且编写了不可预测的代码。
不要担心结果也是不可预测的。
对代码进行看似不相关的微小更改可以使优化器以不同的方式执行任务。
发布于 2015-10-22 21:41:17
有一篇关于内存模型的文章:http://igoro.com/archive/volatile-keyword-in-c-memory-model-explained/
在“内存模型和.NET操作”部分中有一个表:“各种.NET操作如何与虚构的线程缓存交互的表”。
正如我所看到的,普通读取不会刷新线程缓存。我认为这意味着第二个任务是在第一个任务完成之后启动的,因为第二个线程读取了“false”值。
下一段代码显示结果"terminated: 0",正如本例所预期的那样。
这部分代码等于第二个示例:
private static void Main(string[] args)
{
NonVolatileTest t = new NonVolatileTest();
Task.Run(() =>
{
var i = 0;
while (t._loop)
{
i++;
}
Console.WriteLine("terminated: {0}", i);
});
//add delay here
Task.Run(() => { t._loop = false; });
Console.ReadLine();
}如果在开始第二个任务之前添加了Thread.Sleep(1000)延迟,则第二个任务将读取未更改的值(true),因为第一个任务尚未完成,并且我们具有与第一个示例中相同的行为,这一点得到了证实。
https://stackoverflow.com/questions/33280880
复制相似问题