首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >内存模型和ThreadPool

内存模型和ThreadPool
EN

Stack Overflow用户
提问于 2015-10-22 20:18:31
回答 2查看 76关注 0票数 4

我有一个类NonVolatileTest:

代码语言:javascript
复制
public class NonVolatileTest 
{
    public bool _loop = true;
}

我有两个代码示例:

1:

代码语言:javascript
复制
private static void Main(string[] args)
{
    NonVolatileTest t = new NonVolatileTest();

    Task.Run(() => { t._loop = false; });

    while (t._loop) ;
    Console.WriteLine("terminated");

    Console.ReadLine();
}

2:

代码语言:javascript
复制
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采用更改后的值。

EN

回答 2

Stack Overflow用户

发布于 2015-10-22 20:45:12

根据C# 5 specification (可以在带注释的C# 4规范中找到相同的段落),在第10.5.3节-易失性字段下,声明:

当字段声明包含volatile修饰符时,该声明引入的字段是volatile字段。对于非易失性字段,重新排序指令的优化技术可能会在多线程程序中导致意外和不可预测的结果,这些多线程程序在没有同步锁的情况下访问字段,例如语句提供的锁(§8.12)。这些优化可以由编译器、运行时系统或硬件执行。对于易失性字段,这样的重新排序优化受到限制:

(我的重点)

因此,这被证明是不可预测的(也就是你无法控制的)。

这两段代码行为不同的事实可以归结为将代码提升到生成的对象(用于闭包)上的方法和不提升它之间的差异。

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

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

在第二种情况下,上述场景略有不同,因为第一种场景有效地“通过某些对象引用读取变量”,而第二种场景有效地“通过this引用读取变量”,这可能会造成差异。

但这里真正的答案是,您倾向于使用优化器,并且编写了不可预测的代码。

不要担心结果也是不可预测的。

对代码进行看似不相关的微小更改可以使优化器以不同的方式执行任务。

票数 2
EN

Stack Overflow用户

发布于 2015-10-22 21:41:17

有一篇关于内存模型的文章:http://igoro.com/archive/volatile-keyword-in-c-memory-model-explained/

在“内存模型和.NET操作”部分中有一个表:“各种.NET操作如何与虚构的线程缓存交互的表”。

正如我所看到的,普通读取不会刷新线程缓存。我认为这意味着第二个任务是在第一个任务完成之后启动的,因为第二个线程读取了“false”值。

下一段代码显示结果"terminated: 0",正如本例所预期的那样。

这部分代码等于第二个示例:

代码语言:javascript
复制
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),因为第一个任务尚未完成,并且我们具有与第一个示例中相同的行为,这一点得到了证实。

票数 -1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/33280880

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档