锁定HashSet以实现并发

Mar*_*kus 4 .net c#

当使用a HashSet<string>检查时,是否之前处理过一个项目(即仅使用AddContains使用).此外,当Contains返回false时,它是不相关的,即使它是在之前添加的...

我没有锁定遇到以下异常:

[IndexOutOfRangeException:索引超出了数组的范围.] System.Collections.Generic.HashSet`1.AddIfNotPresent(T value)+6108128

仅锁定Add调用是否足够?

以下似乎永远有效 - 但这不是证据......

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

为了使其精确:我知道Contains没有锁定就可以进行线程安全.如果上面的代码可能抛出异常或者可能破坏底层数据结构的内部状态(= HashSet),那么我的问题是(接受误报).

ang*_*son 8

不,仅仅锁定是不够的Add.

它不会崩溃的事实只会告诉您它在测试期间没有崩溃.

你无法保证:

  • 它将来不会崩溃
  • 它会产生正确的结果

如果以多线程方式使用,则非线程安全数据结构无法保证.

你需要:

  • 锁定每次通话
  • 使用线程安全数据结构,该结构是为支持此方案而构建的

如果使用与散列集不同的数据结构(如字典),您甚至可能需要锁定多语句,因为这可能仍然会失败:

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

var value = ExpensiveCallToObtainValue();
lock (dLock)
    d.Add("test", value);
Run Code Online (Sandbox Code Playgroud)

在调用ContainsKey和调用Add另一个线程之间可能已经插入了该键.

要在不使用线程安全数据结构的情况下正确处理此问题,请在同一个锁中包含两个操作:

lock (dLock)
{
    if (!d.ContainsKey("test"))
        d.Add("test", ExpensiveCallToObtainValue());
}
Run Code Online (Sandbox Code Playgroud)

  • 答案是你不能保证它不会. (3认同)