2009-01-12 85 views
27

C#中的字符串是不可變的並且是線程安全的。但是當你有一個公共的getter屬性時呢?就像這樣:字符串屬性本身是否是線程安全的?

public String SampleProperty{ 
    get; 
    private set; 
} 

如果我們有兩個線程,第一個是叫「弄」,並在「相同」的時間,二是呼籲「設置」,會發生什麼?

恕我直言集必須做出一個鎖是線程安全的是這樣的:保證是原子,所以有

private string sampleField; 
private object threadSafer = new object(); 

public String SampleProperty{ 
    get{ return this.sampleField; } 
    private set{ 
     lock(threadSafer){ 
      sampleField = value; 
     } 
    } 
} 
+0

「要求」是:所有使用(讀取)屬性的線程必須具有相同/最新的值。但只有對象本身修改了這個值。 關鍵字'揮發性'應該保證這一點,或不? – TomTom 2009-01-12 10:37:53

回答

37

大多數答案都使用「原子」一詞,就好像原子變化是所有需要的一樣。他們通常不是。

這已在評論中提及,但通常不在答案中 - 這是我提供此答案的唯一原因。 (關於以較粗粒度鎖定以允許諸如追加之類的問題,也是完全有效的。)

通常您想要一個讀線程查看最新的變量/屬性的值。那isn't guaranteed by atomicity。作爲一個簡單的例子,這裏有一個辦法阻止一個線程:

class BackgroundTaskDemo 
{ 
    private bool stopping = false; 

    static void Main() 
    { 
     BackgroundTaskDemo demo = new BackgroundTaskDemo(); 
     new Thread(demo.DoWork).Start(); 
     Thread.Sleep(5000); 
     demo.stopping = true; 
    } 

    static void DoWork() 
    { 
     while (!stopping) 
     { 
       // Do something here 
     } 
    } 
} 

DoWork完全可能永遠循環下去,儘管在寫布爾變量是原子 - 沒有什麼可以從緩存的值停止JIT stopping in DoWork。要解決這個問題,你需要鎖定,使變量volatile或使用明確的內存屏障。這也適用於字符串屬性。

15

引用類型字段的get/set(ldfld/stfld)是(IIRC)這裏不應有任何腐敗風險。因此,它應該是線程安全的從角度,而我個人倒在更高層次上對數據進行鎖定 - 即

lock(someExternalLock) { 
    record.Foo = "Bar"; 
} 

或可能:

lock(record.SyncLock) { 
    record.Foo = "Bar"; 
} 

這使您可以進行多次讀取/更新到同一個對象作爲原子操作,以便其他線程無法獲得無效的對象狀態

+0

TomTom:正如Andreas Huber在我的回答中的評論中指出的那樣,您自動鎖定的鎖定會自動發生,但它不會保護您免受兩個線程嘗試修改屬性的影響,這就是爲什麼Marc建議鎖定屬性更高級別),而不是領域(更低級別)。 – 2009-01-12 09:47:13

+0

(題外話,但我會歡迎來自誰捐贈的反饋意見;我個人不會 - 如果回覆中有問題,請告訴我) – 2009-01-12 10:04:37

+0

Marc:我的「要求」是所有剛讀取值的線程總是具有最新的值。只有「原點」對象應該操縱該值(因此,私人設定者)。所以關鍵字'volatile'應該符合我的需求。希望。 – TomTom 2009-01-12 10:31:39

4

設置字符串是一個原子操作,即您將獲得n新字符串或舊字符串,你永遠不會得到垃圾。

如果您正在做一些工作,例如

obj.SampleProperty = "Dear " + firstName + " " + lastName; 

然後字符串concatination全部發生在調用set之前,因此sampleField將始終是新字符串或舊字符串。

但是,如果您的字符串concatination代碼是自引用的,例如

obj.SampleProperty += obj.SampleProperty + "a"; 

和其他地方的另一個線程你有

obj.SampleProperty = "Initial String Value"; 

然後,你需要鎖定。

考慮你正在使用int。如果你正在分配給int,並且你從int得到的任何值都是有效的,那麼你不需要鎖定它。

但是,如果INT是保持由兩個或多個線程處理,對於數是準確的微件的數量的計數,你需要鎖定INT。 字符串的情況也是如此。

我感覺我沒有解釋這很好,希望它幫助。

感謝

BW

0

這是線程安全的,而無需任何鎖定。字符串是引用類型,所以只修改字符串的引用。引用是一種類型保證是原子的(32位系統上的Int32和64位的Int64)。

0

你的第二個代碼示例肯定是不對的,因爲對變量的訪問鎖只有當他們在所有地方使用了預期的效果(無論是獲得集),所以get還需要鎖。

但是,當獲取並設置引用類型字段作爲像這樣的屬性時,則添加鎖定語句不會添加任何值。指向指針的賦值在.NET環境中保證是原子的,如果多個線程正在改變一個屬性,那麼無論如何你都有一個固有的競爭條件(線程可能會看到不同的值;這可能是也可能不是問題)指向鎖定。

因此,它的作用是,第一段代碼很好。但是,您是否真的想將固有的競爭條件構建到多線程應用程序中是另一回事。