2015-10-13 63 views
2

使用HashSet<string>檢查項目之前是否處理過一個項目(即僅使用了AddContains)。此外,它是不相關的,當包含返回false,即使它之前加入...併發鎖定HashSet

我遇到不鎖定以下異常:

[IndexOutOfRangeException:索引是該數組的範圍之外] System.Collections.Generic.HashSet`1.AddIfNotPresent(T值)6108128

是否足夠,只鎖定在添加通話?

繼似乎永遠工作 - 但是這不是一個證明......

HashSet<string> hashSet = new HashSet<string>(); 
Parallel.ForEach(GetString(), h => 
{ 
    hashSet.Contains(h); 
    lock(hashSetLock) 
    { 
     hashSet.Add(h); 
    } 
    hashSet.Contains(h); 
}); 

爲了使它精確:我知道這是不是線程安全調用Contains沒有鎖。我的問題是(接受誤報),如果上面的代碼可能會拋出一個異常,或者可能會破壞底層數據結構(= HashSet)的內部狀態。

+3

這是您的實際代碼?因爲它沒有多大意義。 –

+0

這只是一個測試重現它... – Markus

+2

這不是一個很好的例子。以線程安全的方式生成這個哈希集有很多種方法。如果您確實想要從多個線程修改集合,則可以使用ConcurrentDictionary ,它使用與鍵和值相同的值。 –

回答

6

不,僅鎖定Add是不夠的。

事實上,它不會崩潰只會告訴你它在測試過程中沒有崩潰。

你不能保證:

  • 它不會在將來崩潰
  • 它會產生正確的結果

非線程安全的數據結構有沒有任何保障,如果使用以多線程的方式。

您需要:

  • 鎖定在每次調用它
  • 使用線程安全的數據結構,一個已經建好如果您使用的是不同的,以支持此方案

數據結構比hashset更像字典,甚至可能需要鎖定多語句,因爲這可能仍然會失敗:

lock (dLock) 
    if (d.ContainsKey("test")) 
     return; 

var value = ExpensiveCallToObtainValue(); 
lock (dLock) 
    d.Add("test", value); 

在對ContainsKey的調用和對Add的調用之間,另一個線程可能已經插入了該密鑰。

要正確處理這個問題,不使用一個線程安全的數據結構,是包含同一把鎖內的兩次操作:

lock (dLock) 
{ 
    if (!d.ContainsKey("test")) 
     d.Add("test", ExpensiveCallToObtainValue()); 
} 
+0

感謝您的回答。我知道,我的測試不是保證/證據(正如我已經在問題中所述:))。爲了縮小範圍,我的問題是,Contains是否可以拋出異常......(誤報對我來說也不是問題......) – Markus

+3

答案是,你不能保證它不會。 –

+0

請添加代碼以正確執行此操作,您可以很容易地採用您提供的錯誤代碼片段來執行正確的操作。 –

0

Contains()的呼叫有什麼意義?他們什麼都不做。如果你想只添加如果設定不包含項目,那麼你就可以做到以下幾點:

if(!hasSet.Contains(h)) 
{ 
    lock(hashSetLock) 
    { 
     if(!hasSet.Contains(h)) 
     { 
     hashSet.Add(h); 
     } 
    } 
} 

有了這個代碼,你不鎖檢查元素的存在,但如果要素是沒有設置你必須在鎖定後再次檢查。你有什麼收穫?如果元素已經存在,則不鎖定。

+0

'HashSet .Contains'不能保證是線程安全的,所以你不能在沒有鎖的情況下真的調用它。 – Joey

1

沒有像其他人所說的那樣,是不是線程安全的做你」重新做。如果底層集合不是線程安全的,則需要鎖定每個操作。

當使用HashSet<T>,不需要有一個ContainsKey檢查,如Add will check if the internal collection already contains the value or not

返回值類型:System.Boolean

真如果元素被添加到 HashSet對象;如果元素已經存在,則返回false。

所以,你可以縮小你的代碼:

private readonly object syncRoot = new object(); 
lock (syncRoot) 
    hashSet.Add(value);