2013-01-21 100 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