Dot-Net

執行緒安全 Dictionary.Add

  • March 31, 2011

Dictionary.Add()僅插入時執行緒安全嗎?

我有一個從多執行緒插入密鑰的程式碼,我還需要鎖定 Dictionary.Add()

添加新密鑰時出現此異常:

Exception Source:    mscorlib
Exception Type: System.IndexOutOfRangeException
Exception Message:   Index was outside the bounds of the array.
Exception Target Site: Insert

雖然它非常罕見。我知道這Dictionary不是執行緒安全的,儘管我認為只有呼叫.Add不會引起任何問題。

字典根本不是執行緒安全,不管你是否只添加它——它有一些內部結構需要保持同步(尤其是當內部雜湊桶被調整大小時)。

您要麼必須圍繞其上的任何操作實現自己的鎖定,要麼如果您在 .Net 4.0 中,您可以使用新的 ConcurrentDictionary - 這絕對是太棒了 - 並且完全是執行緒安全的。

另一種選擇(更新)

也就是說 - 您可以使用另一種技術 - 但它需要進行一些調整,具體取決於您插入字典的數據類型,以及是否保證所有鍵都是唯一的:

給每個執行緒它自己插入的私有字典。

當每個執行緒完成時,將所有字典整理在一起,並將它們合併成一個更大的字典;如何處理重複鍵取決於您。例如,如果您通過鍵記憶體項目列表,那麼您可以簡單地將每個相同鍵的列表合併為一個並將其放入主字典中。

官方回復回复:性能(你接受後)

因此,正如您的評論所說,您需要了解性能等方面的最佳方法(鎖定或合併)。我無法告訴您這將是什麼;最終需要對其進行基準測試。不過,我會看看是否可以提供一些指導:)

首先 - 如果您知道您的 Dictionar(y/ies) 最終需要多少項目,請使用(int)建構子來最小化調整大小。

合併操作可能是最好的;因為沒有任何執行緒會相互干擾。除非兩個對象共享同一個密鑰所涉及的過程特別冗長;在這種情況下,在操作結束時強制這一切發生在單個執行緒上可能最終會通過並行化第一階段將所有性能增益歸零!

同樣,由於您將有效地複製字典,因此存在潛在的記憶體問題,因此如果最終結果足夠大,您最終可能會消耗大量資源;但是,授予-他們將被釋放。

如果在密鑰已經存在時需要線上程級別做出決定,那麼您將需要一個 lock(){} 構造。

在字典中,這通常採用以下形式:

readonly object locker = new object();
Dictionary<string, IFoo> dictionary = new Dictionary<string, IFoo>();

void threadfunc()
{
 while(work_to_do)
 {
   //get the object outside the lock
   //be optimistic - expect to add; and handle the clash as a 
   //special case
   IFoo nextObj = GetNextObject(); //let's say that an IFoo has a .Name
   IFoo existing = null;
   lock(locker)
   {
     //TryGetValue is a god-send for this kind of stuff
     if(!dictionary.TryGetValue(nextObj.Name, out existing))
       dictionary[nextObject.Name] = nextObj;
     else
       MergeOperation(existing, nextObject);
   }
 }
}

現在,如果那MergeOperation真的慢;那麼您可能會考慮釋放鎖,創建一個複製對象,表示現有對象和新對象的合併,然後重新獲取鎖。但是 - 您需要一種可靠的方法來檢查現有對象的狀態在第一個鎖和第二個鎖之間是否沒有更改(版本號對此很有用)。

引用自:https://stackoverflow.com/questions/5506325