10

我想大多数人都知道在发布模式下构建时会发生以下问题(代码取自C# 中的线程):

static void Main()
{
  bool complete = false; 

  var t = new Thread (() =>
  {
    bool toggle = false;
    while (!complete) toggle = !toggle;
  });

  t.Start();
  Thread.Sleep (1000);

  complete = true;
  t.Join();        // Blocks indefinitely
}

由于编译器优化缓存的值,complete从而防止子线程看到更新的值。

但是,稍微更改一下上面的代码:

class Wrapper
{
    public bool Complete { get; set; }
}

class Test
{
    Wrapper wrapper = new Wrapper();

    static void Main()
    {
        var test = new Test();
        var t = new Thread(() =>
        {
            bool toggle = false;
            while (!test.wrapper.Complete) toggle = !toggle;
        });

        t.Start();
        Thread.Sleep(1000);

        test.wrapper.Complete = true;
        t.Join();        // Blocks indefinitely
    }
}

使问题消失(即子线程能够在 1 秒后退出),而无需使用volatile、内存栅栏或任何其他引入隐式栅栏的机制。

完成标志的附加封装如何影响其在线程之间的可见性?

4

2 回答 2

6

我想你的问题有答案:

由于编译器优化缓存了 complete 的值,从而阻止了子线程看到更新的值。

编译器/JIT 优化只要有意义/被认为是安全和合理的实施,就会执行。因此,您发现优化没有按照您期望的方式执行的情况 - 可能有充分的理由(有人检测到这种使用模式和块优化)或者它恰好没有被优化(很可能)。

于 2013-01-21T18:41:57.510 回答
0

第一种情况是对局部变量的简单别名传播。编译器非常积极地这样做(http://en.m.wikipedia.org/wiki/Aliasing_(computing))第二种情况,即使由于属性与成员变量的语法相似性而看起来相同,也有方法调用(getter /setter) 并且不能简单地简化。

于 2013-01-21T18:56:41.510 回答