2012-09-13 32 views
4

我使用ConcurrentDictioanry<string, HashSet<string>>來訪問跨多個線程的一些數據。AddOrUpdate時鎖定ConcurrentDictionary?

我在this article(向下滾動)中看到方法AddOrUpdate沒有在鎖中執行,所以它可能危及線程安全。

我的代碼如下:

//keys and bar are not the concern here 
ConcurrentDictioanry<string, HashSet<string>> foo = new ...; 
foreach(var key in keys) { 
    foo.AddOrUpdate(key, new HashSet<string> { bar }, (key, val) => { 
     val.Add(bar); 
     return val; 
    }); 
} 

我應該附上一個lock聲明AddOrUpdate調用,以確保一切是線程安全的?

回答

5

鎖定在AddOrUpdate自己不會幫助 - 你仍然必須鎖定每次你從集讀。

如果你打算把這個集合看作線程安全的,你真的需要這些值也是線程安全的。理想情況下,您需要一個ConcurrentSet。現在這個框架不存在(除非我錯過了某些東西),但是你可以創建你自己的ConcurrentSet<T>,它使用了ConcurrentDictionary<T, int>(或者你喜歡的任何TValue)作爲它的底層數據結構。基本上,你會忽略字典中的價值,只要將密鑰的存在視爲重要的一部分。

您不需要執行ISet<T>中的所有內容 - 只需要實際需要的位。

然後,您會在應用程序代碼中創建一個ConcurrentDictionary<string, ConcurrentSet<string>>,並且您不在需要鎖定。

+0

如果通過API訪問(讀寫)「ConcurrentDictionary」實例,該怎麼辦?所以客戶端代碼實際上並不會注意到有'ConcurrentDictionary',那麼我可以安全地在我的API的公共方法中進行鎖定,對吧?我只是努力去理解這裏的東西...... – Paul

+1

@Paul:是的,你*可以*確保你每次訪問任何東西時都會鎖定。但是那麼你認爲ConcurrentDictionary真的會買你什麼? –

+0

是的,你說得對。 – Paul

0

該文章指出添加委託沒有在字典的鎖中執行,並且您獲得的元素可能不是該添加委託在該線程中創建的元素。這不是線程安全問題;字典的狀態將保持一致,並且所有調用者都將獲得相同的實例,即使爲每個實例創建了不同的實例(並且除了一個都會被刪除)。

4

你需要修復這段代碼,它會產生很多垃圾。即使不需要,也可以創建新的HashSet。使用其他超載,即接受值爲工廠代理的那一個。因此,只有在字典中尚未出現密鑰時才創建HashSet。

valueFactory可能被多次調用如果多個線程同時嘗試添加的關鍵相同的值,它不存在。非常低的可能性,但不是零。這些哈希集中只有一個會被使用。沒有問題,創建HashSet沒有可能導致線程麻煩的副作用,額外的副本只是垃圾收集。

+1

謝謝,通過使用其他超載修復。 – Paul

-1

似乎更好的答案是使用Lazy,通過article傳遞給委託的方法。

另一篇好文章Here關於延遲加載添加委託。