c# – 内存模型和ThreadPool

我有一个类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

假设:

这个’bug’可能与TaskScheduler有关.我认为,在JIT为编译和运行完成第二个任务之前,第一个任务已经完成,因此JIT获取了更改的值.

最佳答案 根据 C# 5 specification(并且在注释的C#4规范中可以找到相同的段落),在10.5.3节 – 易失性字段中,这表示:

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:

(我的重点)

所以这被证明是不可预测的(也就是你的控制之外).

这两段代码行为不同的事实可归结为将代码提升到生成对象上的方法(用于闭包)而不是提升它的区别.

我的心灵代码阅读眼睛告诉我,这是第一种情况可能发生的情况:

>任务已启动,但在调用委托中的实际代码之前,这会产生开销
>在调用委托之前,主程序继续并设法启动循环,单次读取控制变量,并继续重用其缓存副本.
>委托最终被执行,但这对循环没有影响,因为它已经读过变量一次,并且没有再次尝试这样做的倾向.

在第二种情况下,由于第一种情况有效地“通过一些对象引用读取变量”而第二种情况有效地“通过该引用读取变量”,这可能会产生差异,因此上述情况略有改变.

但这里真正的答案是你很容易使用优化器并编写不可预测的代码.

不要惊慌,结果也是不可预测的.

对代码进行微小的看似无关的更改可以使优化器以不同的方式执行操作.

点赞