2010-06-27 104 views
15

閱讀MSDN文檔時,它總是讓您知道類是否是線程安全的。我的問題是你如何設計一個類是線程安全的?我不是在說用鎖定來調用類我的意思是我正在爲Microsoft創建XXX class \ object而工作,我想說它是「線程安全的」,我需要做什麼?設計線程安全類

+1

參見例如http://stackoverflow.com/questions/564319/achieving-thread-safety。 – 2010-06-27 20:05:59

+0

我不想爲線程安全測試我在問你需要考慮到什麼設計...... – Shane 2010-06-27 20:20:00

回答

8

使線程安全的最簡單也是最簡單的方法是使其成爲immutable。它的美妙之處在於你永遠不必再爲鎖定而煩惱。配方:使C#中的所有實例變量readonly(Java中爲final)。

  • 一個不可變的對象,一旦在構造函數中創建並初始化,就不能改變。
  • 不可變對象是線程安全的。期。
  • 這與只有常量的類不一樣。
  • 對於系統的可變部分,您仍然需要考慮並處理鎖定/同步屬性。這是首先編寫不可變類的一個原因。

看到這個question以及。

+1

我不同意你的原因。 1.我們合作的不同種類的不變性。 http://blogs.msdn.com/b/ericlippert/archive/2007/11/13/immutability-in-c-part-one-kinds-of-immutability.aspx 2.'final'是Java,你意味着讓他們常量?如果是,我不同意。使類常量中的所有字段與使線程安全區別很大 – ram 2010-06-27 22:04:22

+1

當您需要線程實際共享數據時(我根據問題假設是這種情況),不可變是無用的。 – Gabe 2010-06-27 23:09:43

+1

最後我的意思是_make確定它在構造函數後有一個值,並且該值只讀一次assigned_。這裏有很多例子,證明了我的觀點,即http://blogs.msdn.com/b/lucabol/archive/2007/12/06/creating-an-immutable-value-object-in-c-part -ii-making-the-class-better.aspx http://pebblesteps.com/post/Introduction-to-Immutability-in-C.aspx – 2010-06-28 05:55:05

4

爲了聲明該類是線程安全的,您將聲明類中的內部數據結構不會因多個線程的併發訪問而被破壞。爲了做出這個斷言,你需要在類內部的關鍵代碼段引入鎖定(用Java同步),這可能會導致它們被多個併發線程執行的損壞。

+0

你不需要引入鎖定。你可以用System.Threading.Interlocked。*來做一些漂亮的事情。 – Spence 2010-06-28 06:21:11

+0

另一個可能更簡單的方法是將所有實例變量標記爲「只讀」以使類不可變。 – 2010-06-28 08:02:30

0

非通用ICollection類爲線程安全提供屬性。 IsSynchronizedSyncRoot。不幸的是你不能設置IsSynchronized。你可以閱讀更多關於它們的信息here

在你的類中你可以有類似IsSynchronized和Syncroot的東西,公開的方法/屬性可以單獨公開,也可以在方法體內檢查。你IsSynchronized將是一個只讀屬性,所以一旦您的實例初始化,您將無法對其進行修改

bool synchronized = true; 
var collection = new MyCustomCollection(synchronized); 
var results = collection.DoSomething(); 

public class MyCustomCollection 
{ 
    public readonly bool IsSynchronized; 
    public MyCustomCollection(bool synchronized) 
    { 
    IsSynchronized = synchronized 
    } 

    public ICollection DoSomething() 
    { 
    //am wondering if there is a better way to do it without using if/else 
    if(IsSynchronized) 
    { 
    lock(SyncRoot) 
    { 
     MyPrivateMethodToDoSomething(); 
    } 
    } 
    else 
    { 
     MyPrivateMethodToDoSomething(); 
    } 

    } 
} 

你可以閱讀更多關於Jared Parson's blog

0

編寫線程安全的集合的文檔不建議類是線程安全的,只有方法是。爲了斷言一個方法是線程安全的,它必須可以同時從多個線程中調用,而不會給出錯誤的結果(如果結果不正確,則返回錯誤的值或進入無效狀態的對象)。

當文件說

任何公共靜態此類型的成員(在Visual Basic中的Shared)是線程安全的 。

這可能意味着該類的靜態成員不會改變共享狀態。

當文件說

所有實例成員都是不 保證線程安全的。

這可能意味着方法具有最小的內部鎖定。

當文件說

的 這個類的所有公共和保護的成員都是線程安全的,並且可以從多個線程 同時使用 。

這可能意味着您可以調用的所有方法都使用適當的鎖定。這些方法也可能不改變任何共享狀態,或者它是一個無鎖的數據結構,通過副設計允許沒有任何鎖定的併發使用。

0

線程安全類是關於保護類中的數據(實例變量)的。最常見的方法是使用關鍵字。最常見的新手錯誤是使用鎖,而不是一個更細粒度鎖整個類:

lock (this) 
{ 
    //do somethnig 
} 

但問題是,它可以給你一個主要性能命中,如果類做一些重要的事情。一般的規則是儘可能短的鎖

你可以在這裏閱讀更多:lock keyword in C#

當你strarted瞭解multithreadnig更深入,你也可以看看ReaderWriterLoch和信號燈。但我建議你只能從lock關鍵字開始。

5

除了這裏的其他優秀答案,還要考慮另一個角度。

如果公共API具有無法以線程安全方式使用的多步操作,則該類的內部數據結構爲100%線程安全是不夠的。

考慮一個已經構建的列表類,不管有多少線程在做什麼,不管它有多少種類型的操作,列表的內部數據結構將始終保持一致並且正常。

考慮以下代碼:

if (list.Count > 0) 
{ 
    var item = list[0]; 
} 

這裏的問題是,Count屬性的讀取,並通過[0]索引的第一個元素的讀數之間,另一個線程可能已經清理出列表的內容。

這種類型的線程安全通常在公共API被創建時被遺忘。在這裏,唯一的解決方案是調用代碼手動鎖定每種此類訪問類型的內容以防止代碼崩潰。要解決這個

一種方式是對列表類型的作者要考慮的典型使用場景,並添加適當的方法類型:

public bool TryGetFirstElement(out T element) 

,那麼你將有:

T element; 
if (list.TryGetFirstElement(out element)) 
{ 
    .... 

想必,TryGetFirstElement以線程安全的方式工作,並且不會返回true,因爲它無法讀取第一個元素值。

+1

值得注意的是,在很多情況下,類可能會指定各種線程安全性。例如,對於'IDictionary '實現來說,指定任何從未被調用過的'Delete'的實例在使用任意數量的讀者和作者時都是線程安全的,前提是沒有兩個作者對同樣的密鑰,並且沒有讀者要求其關聯值被修改(但在修改之前存在)的密鑰,除非'TValue'是引用類型,否則承諾完全線程安全。 – supercat 2014-10-31 17:26:54