2010-01-25 57 views
12

通常,當我想一類是線程安全的,我這樣做如下:這是一個很好的設計在C#中創建線程安全類?

public class ThreadSafeClass 
{ 
    private readonly object theLock = new object(); 

    private double propertyA; 
    public double PropertyA 
    { 
     get 
     { 
      lock (theLock) 
      { 
       return propertyA; 
      } 
     } 
     set 
     { 
      lock (theLock) 
      { 
       propertyA = value; 
      } 
     } 
    } 

    private double propertyB; 
    public double PropertyB 
    { 
     get 
     { 
      lock (theLock) 
      { 
       return propertyB; 
      } 
     } 
     set 
     { 
      lock (theLock) 
      { 
       propertyB = value; 
      } 
     } 
    } 

    public void SomeMethod() 
    { 
     lock (theLock) 
     { 
      PropertyA = 2.0 * PropertyB; 
     } 
    } 
} 

它的工作原理,但它是非常冗長。有時我甚至會爲每種方法和屬性創建一個鎖對象,以創建更多的冗長和複雜性。

我知道也可以使用Synchronization屬性來鎖定類,但我不確定這個比例有多好 - 因爲我經常期望有數十萬甚至數百萬個線程安全實例對象。這種方法會爲類的每個實例創建一個同步上下文,並且要求該類從ContextBoundObject派生,因此不能從其他任何派生出來 - 因爲C#不允許多繼承 - 這是一個顯示限制器在許多情況下。

編輯:正如幾位響應者所強調的那樣,沒有「銀彈」線程安全的類設計。我只是想了解我使用的模式是否是最好的解決方案之一。當然,在任何特定情況下的最佳解決方案都依賴於問題。以下幾個答案包含應考慮的替代設計。

編輯:此外,有多個線程安全的定義。例如,在上面我的實現,下面的代碼不會是線程安全的:

var myObject = new ThreadSafeClass(); 
myObject.PropertyA++; // NOT thread-safe 

因此,沒有上面的類定義代表了一個好辦法?如果不是,那麼對於具有類似行爲的設計,您會推薦哪種設計,這些設計對於類似的用途是線程安全的?

+4

請先搜索:http://stackoverflow.com/search?q=What+is+the+Best+Practice+for+Creating+Thread-Safe+Classes+in+C%23 – 2010-01-25 20:06:33

+0

嗯。在你的代碼中,你使用相同的鎖對象來讀取PropertyA和PropertyB。這意味着一個讀取PropertyA的線程會鎖定另一個嘗試讀取PropertyB的內容,直到第一個線程完成。那是你要的嗎?如果這是我的課程,我會爲每個吸氣器創建一個單獨的讀取鎖定器,然後設置者也必須尊重該鎖定。請參閱下面的答案。 – 2010-01-25 20:44:48

+0

我只是低估了你,因爲沒有單一的方法來產生一個線程安全的類。對這個問題的任何回答都有可能給未來的讀者帶來錯誤的印象。 – 2010-01-26 01:06:09

回答

8

對於多線程問題沒有「一刀切」的解決方案。做一些關於創建不可變類的研究,並瞭解不同的同步原語。

這是一個半不可改變的程序員-不可改變類的一個例子。

public class ThreadSafeClass 
{ 
    public double A { get; private set; } 
    public double B { get; private set; } 
    public double C { get; private set; } 

    public ThreadSafeClass(double a, double b, double c) 
    { 
     A = a; 
     B = b; 
     C = c; 
    } 

    public ThreadSafeClass RecalculateA() 
    { 
     return new ThreadSafeClass(2.0 * B, B, C); 
    } 
} 

本示例將您的同步代碼移動到另一個類中,並將對實例的訪問序列化。實際上,在任何給定的時間,你並不希望在一個對象上運行多個線程。

public class ThreadSafeClass 
{ 
    public double PropertyA { get; set; } 
    public double PropertyB { get; set; } 
    public double PropertyC { get; set; } 

    private ThreadSafeClass() 
    { 

    } 

    public void ModifyClass() 
    { 
     // do stuff 
    } 

    public class Synchronizer 
    { 
     private ThreadSafeClass instance = new ThreadSafeClass(); 
     private readonly object locker = new object(); 

     public void Execute(Action<ThreadSafeClass> action) 
     { 
      lock (locker) 
      { 
       action(instance); 
      } 
     } 

     public T Execute<T>(Func<ThreadSafeClass, T> func) 
     { 
      lock (locker) 
      { 
       return func(instance); 
      } 
     } 
    } 
} 

下面是如何使用它的一個簡單示例。它可能看起來有點笨重,但它允許您一次對該實例執行許多操作。

var syn = new ThreadSafeClass.Synchronizer(); 

syn.Execute(inst => { 
    inst.PropertyA = 2.0; 
    inst.PropertyB = 2.0; 
    inst.PropertyC = 2.0; 
}); 

var a = syn.Execute<double>(inst => { 
    return inst.PropertyA + inst.PropertyB; 
}); 
+0

我明白你的方法 - 我發現在很多情況下它會是最好的。但是,它不允許使用屬性語法更改A,B和C,並且如果更新非常頻繁,則會產生性能問題,因爲每次更新都需要創建一個新對象並讓GC處理舊對象。 – 2010-01-25 20:33:35

+0

不適合這樣的班級。但對於任何類型的商業對象,您都是正確的。 – ChaosPandion 2010-01-25 20:36:01

+1

這只是一個不可變類的例子。雖然說它是「線程安全」的技術上是正確的(在多個同時線程的交互不會導致腐敗或不良行爲的意義上),但它並不是一種模型,「這就是所有'線程安全'類應該如何進行的」 。 – 2010-01-25 20:51:02

3

請記住,術語「線程安全」不是特定的;通過使用Monitor鎖,您在此處所做的操作將更準確地稱爲「同步」。

也就是說,圍繞同步代碼的冗長是非常不可避免的。

lock (theLock) 
{ 
    propertyB = value; 
} 

到這一點::您可以通過打開這樣的事情削減一些在你的榜樣空白的

lock (theLock) propertyB = value; 

至於這是否是適合你的正確方法,我們真的需要更多的信息。同步只是「線程安全」的一種方法;不可變對象,信號量等都是適合不同用例的不同機制。對於你提供的簡單例子(看起來你試圖確保獲取或設置操作的原子性),那麼它看起來像你做了正確的事情,但如果你的代碼旨在更多的是舉例而言,事情可能並非如此簡單。

0

根據我上面的評論 - 如果你想允許同時讀者但只有一個作家允許,它會變得有點多毛。請注意,如果您使用.NET 3.5,則對此類型的模式使用ReaderWriterLockSlim而不是ReaderWriterLock

public class ThreadSafeClass 
{ 
    private readonly ReaderWriterLock theLock = new ReaderWriterLock(); 

    private double propertyA; 
    public double PropertyA 
    { 
     get 
     { 
      theLock.AcquireReaderLock(Timeout.Infinite); 
      try 
      { 
       return propertyA; 
      } 
      finally 
      { 
       theLock.ReleaseReaderLock(); 
      } 
     } 
     set 
     { 
      theLock.AcquireWriterLock(Timeout.Infinite); 
      try 
      { 
       propertyA = value; 
      } 
      finally 
      { 
       theLock.ReleaseWriterLock(); 
      } 
     } 
    } 

    private double propertyB; 
    public double PropertyB 
    { 
     get 
     { 
      theLock.AcquireReaderLock(Timeout.Infinite); 
      try 
      { 
       return propertyB; 
      } 
      finally 
      { 
       theLock.ReleaseReaderLock(); 
      } 
     } 
     set 
     { 
      theLock.AcquireWriterLock(Timeout.Infinite); 
      try 
      { 
       propertyB = value; 
      } 
      finally 
      { 
       theLock.ReleaseWriterLock(); 
      } 
     } 
    } 

    public void SomeMethod() 
    { 
     theLock.AcquireWriterLock(Timeout.Infinite); 
     try 
     { 
      theLock.AcquireReaderLock(Timeout.Infinite); 
      try 
      { 
       PropertyA = 2.0 * PropertyB; 
      } 
      finally 
      { 
       theLock.ReleaseReaderLock(); 
      } 
     } 
     finally 
     { 
      theLock.ReleaseWriterLock(); 
     } 
    } 
} 
+0

如果你有10個屬性而不是兩個? – 2010-01-25 20:52:00

+4

知道你的工具,框架有ReaderWriterLockSlim來執行這種鎖定:http://msdn.microsoft.com/en-us/library/system.threading.readerwriterlockslim.aspx – 2010-01-25 20:59:42

+0

這正是我問你是否想要具有完全同步或不同步的屬性。這完全取決於你的需求。您交易複雜性的靈活性。 Chris對ReaderWriterLockSlim(或者ReaderWriterLock,如果你想要HEAVYWEIGHT版本的)的建議將會使編寫者序列化。 – 2010-01-25 21:49:07

1

您可能會發現Interlocked類有幫助。它包含幾個原子操作。

1

有一兩件事你可以做,可以幫助您避免額外的代碼是使用類似PostSharp自動注入這些lock語句到你的代碼,即使你有數百人。所有你需要的是一個屬性附加到類,和屬性的實現,將添加額外的鎖定變量。

4

我知道這聽起來像一個聰明的一個**答案,但...開發線程安全類是真正瞭解多線程的,關於它的意義,它的複雜性它有什麼暗示的最佳途徑。沒有銀彈。

說真的......不要嘗試多線程(在生產場景中,我的意思是),直到你知道自己陷入了什麼......它可能是一個巨大的錯誤。你應該知道操作系統和你選擇的語言的同步原語(在這種情況下,我猜這是Windows下的C#)。

對不起,我不會放棄的只是代碼,只是做一個類線程安全的。那是因爲它不存在。一個完全線程安全的類可能會比僅僅避免線程慢,並且可能會成爲任何你正在做的事情的瓶頸......有效地使用線程來取消你所做的任何事情。

+0

我知道沒有銀彈。我有很多使用上述模式的生產代碼 - 我同意你不應輕視多線程。但是,我的應用程序非常強大,並且不會(目前)受到任何死鎖或競爭條件的影響 - 儘管它已經在過去。我並不是說這種模式或變體就是解決所有多線程問題的答案。我只是問是否有更優雅的方式來獲得上述代碼所具有的行爲。它對我來說似乎並不理想,我正在試圖利用社區的智慧。 – 2010-01-26 03:32:37

+5

我不得不說這是非常糟糕的建議。這就好像是說每個人拿起鋸子之前都應該成爲一名高級工匠。你必須從某個地方開始,這不是一個糟糕的地方。 – 2010-01-26 03:36:15

+0

我必須同意喬納森對此的看法。 – ChaosPandion 2010-01-26 04:02:54

3

由於沒有其他人似乎在做它,這裏是你的特殊設計的一些分析。

  • 想閱讀單一的財產? Threadsafe
  • 想要更新到任何單個屬性? Threadsafe
  • 想要讀取一個屬性,然後根據其原始值更新它?不是線程安全的

線程2可以更新線程1的讀取和更新之間的值。

  • 想要同時更新兩個相關的屬性?不是線程安全的

您可能最終得到屬性A具有線程1的值和屬性B具有線程2的值。

  1. 線程1更新
  2. 線程2更新
  3. 線程1個更新乙
  4. 線程2更新乙

    • 想在同一時間閱讀兩個相關的屬性?不是線程安全的

同樣,你可以在第一和第二讀取之間中斷。

我可以繼續,但你明白了。 Threadsafety完全基於您如何計劃訪問對象以及您需要做出什麼承諾。