2012-02-23 70 views
7

我今天遇到了一些單碼在我們的代碼庫,我不知道,如果下面是線程安全的:的C原子性#合併運算

public static IContentStructure Sentence{ 
    get { 
     return _sentence ?? (_sentence = new Sentence()); 
    } 
} 

這種說法是等價於:

if (_sentence != null) { 
     return _sentence; 
} 
else { 
    return (_sentence = new Sentence()); 
} 

我相信?只是一個編譯器技巧,並且生成的代碼仍然不是原子的。換句話說,在將_sentence設置爲新句子並返回之前,兩個或更多線程可能會發現_sentence爲空。

爲了保證原子性,我們不得不鎖定的代碼位:

public static IContentStructure Sentence{ 
    get { 

     lock (_sentence) { return _sentence ?? (_sentence = new Sentence()); } 
    } 
} 

那是正確的?

+2

http://csharpindepth.com/Articles/General/Singleton.aspx – SLaks 2012-02-23 19:58:52

+2

除了你不能鎖定的東西是null,所以你的解決方案將永遠不會工作。 – vcsjones 2012-02-23 20:03:40

+0

好點。是的,你必須創建另一個對象來鎖定。接得好。 – Adam 2012-02-23 20:09:59

回答

11

你是對的;它完全不是線程安全的。

1

您可以使用Interlocked.CompareExchangenull獲得原子型的?? -queque操作。

// I made up my own Sentence type 
Sentence current = null; 
var whenNull = new Sentence() {Text = "Hello World!"}; 

var original = Interlocked.CompareExchange(ref current, new Sentence() { Text = "Hello World!" }, null); 

Assert.AreEqual(whenNull.Text, current.Text); 
Assert.IsNull(orig); 

// try that it won't override when not null 
current.Text += "!"; 
orig = Interlocked.CompareExchange(ref current, new Sentence() { Text = "Hello World!" }, null); 

Assert.AreEqual("Hello World!!", current.Text); 
Assert.IsNotNull(orig); 
+0

這方面的缺點是,你爲每次傳球都創建了一個新的「Sentence」,不是嗎? – 2012-02-24 16:32:46

+2

@DrewNoakes:你是對的。只有在電流爲零的情況下進行比較交換才更爲習慣。然後,只有兩次創建對象纔是不可能的比賽。如果兩次創建對象都是不可接受的,那麼您可以使用其他技術。 – 2012-02-24 16:44:17

+0

所以,你應該能夠做到以下幾點:var x = current ?? Interlocked.CompareExchange(ref current,new Sentence(),null)??當前; – 2012-09-14 20:14:46

15

我今天遇到了一些單碼在我們的代碼庫

你有整個代碼庫這樣的混淆代碼?此代碼做同樣的事情:

if (_s == null) 
    _s = new S(); 
return _s; 

並且大約有一千倍更容易閱讀。

我相信?只是一個編譯器技巧,並且生成的代碼仍然不是原子的

你是對的。 C#提供以下原子性保證:

對以下數據類型的讀寫是原子性的:bool,char,byte,sbyte,short,ushort,uint,int,float和reference類型。另外,在前面的列表中讀取和寫入帶有基礎類型的枚舉類型也是原子的。其他類型(包括long,ulong,double和decimal)以及用戶定義類型的讀取和寫入不保證是原子性的。除了爲此目的而設計的庫函數之外,不能保證原子讀取 - 修改 - 寫入,例如在增量或減量的情況下。

null合併運算符不在保證列表中。

爲了保證原子性,我們不得不鎖定的代碼位:

lock (_sentence) { return _sentence ?? (_sentence = new Sentence()); } } }  

老天爺沒有。立即崩潰!

正確的事情做的是一個:

  • 停止試圖編寫多線程代碼。
  • 使用其中一個安全單例模式Jon Skeet在其單頁上記錄有關單例的單例。
  • 使用Lazy<T>類。
  • 鎖定專用於鎖定該變量的對象。
  • 使用聯鎖比較交換來執行原子測試並進行設置。