2013-01-21 106 views
9

我想大多数人都知道了以下问题在Release模式建设(从Threading in 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,内存栅栏,或任何其他引入隐式栅栏的机制。

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

+1

您的代码不能保证能正常工作,但也不能保证失败。您不应该依赖编译器优化来实现正确性(或者在这种情况下不正确)。 – svick

+1

@svick:我其实不是。这只是我不小心注意到的事情。 – Tudor

+1

[可重复使用的挥发性使用示例]的可能重复(http://stackoverflow.com/questions/6164466/a-reproducable-example-of-volatile-usage) –

回答

6

我觉得你在你的问题有答案:

由于编译器优化缓存的完整的价值,从而防止子线程从曾经看到更新后的值。

编译器/ JIT优化在有意义/视为安全合理的情况下执行。因此,您发现未按预期方式执行优化的情况 - 可能有一个很好的原因(有人检测到此使用模式和块优化),或者它只是不被优化(很可能)。

+5

重点在于这是未定义的行为。编译团队可以决定明天优化属性,就像第一个例子一样,或者他们不能优化第一个(或者做得不同),这样它就不会停下来。 – Servy

+0

我同意你的观点,很可能情况如此。我只是想知道在确定这样的代码是否会失败时我是否可以发现一个模式。 – Tudor

0

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

+0

在第一种情况下,它不是一个简单的值设置..运行时不能只是“忽略”类似的东西 –

+0

不明白你的意思是“不是一个简单的设置值”。别名传播是一种编译器技术,用于简化或替换变量http://en.wikipedia.org/wiki/Aliasing_(computing) – nakhli