【问题标题】:Locking HashSet for concurrency [duplicate]为并发锁定 HashSet [重复]
【发布时间】:2015-10-13 07:37:19
【问题描述】:

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

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

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

只锁定 Add 调用就足够了吗?

跟随似乎永远有效 - 但这不是证据......

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

准确地说:我知道在没有锁的情况下调用Contains 不是线程安全的。我的问题是(接受误报)上述代码是否会引发异常或破坏底层数据结构(=HashSet)的内部状态。

【问题讨论】:

  • 这是您的实际代码吗?因为它没有多大意义。
  • 这只是一个重现它的测试......
  • 这不是一个很好的例子。有很多方法可以以线程安全的方式生成此哈希集。如果你真的想从多个线程修改集合,你可以使用 ConcurrentDictionary ,它使用与 key 和 value 相同的值。

标签: c# .net


【解决方案1】:

打电话给Contains() 有什么意义?他们什么都不做。如果您只想在集合不包含项目时添加,则可以执行以下操作:

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

使用此代码,您无需锁定以检查元素的存在,但如果未设置元素,则必须在锁定后再次检查。你有什么收获?如果元素已经存在,则不要锁定。

【讨论】:

  • HashSet&lt;T&gt;.Contains 不能保证是线程安全的,所以没有锁你不能真正调用它。
  • 加锁是很昂贵的在哈希集中。除非有人可以解释如果未锁定包含可能会以何种方式失败(如抛出或导致损坏),那么对于需要高性能(至少有时)的代码来说,这是一个很好的折衷方案
【解决方案2】:

不,仅锁定Add 是不够的。

它没有崩溃的事实只是告诉您它在测试期间没有崩溃。

您不能保证:

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

如果以多线程方式使用,非线程安全的数据结构没有任何保证。

您需要:

  • 锁定每次调用它
  • 使用线程安全的数据结构,它是为支持这种情况而构建的

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

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());
}

【讨论】:

  • 感谢您的回答。我知道,我的测试不是保证/证明(正如我在问题中已经说过的:))。为了缩小范围,我的问题是,包含是否会引发异常......(误报对我来说也不是问题......)
  • 答案是你不能保证它不会。
  • 请添加代码以正确执行,很容易将您提供的错误代码sn-p用于正确的执行方式。
  • 在我的情况下,没有对 HashSet 进行删除,但正在使用添加。我做了一些繁重的测试,并没有在 Contains 调用中捕获一个异常。我还检查了 Reflector 中的 Contains 代码,如果没有进行 Remove,我找不到会导致异常的东西。但是,当我阅读此答案并将包含使用 goto 的 try catch 包围时,我感到不舒服。是的,代码看起来很难看,但我的测量结果表明它比使用锁要快,因为从未发生过异常。
  • 我不能说它崩溃,但是有问题的类不是线程安全的。确切的行为没有准确记录,因为它不是线程安全的。据我所知,它会烧毁你的房子或赶走你的狗。
【解决方案3】:

不,正如其他人所说,做你正在做的事情不是线程安全的。如果底层集合不是线程安全的,则需要锁定每个操作。

使用HashSet&lt;T&gt; 时,不需要ContainsKey 检查,如Add will check if the internal collection already contains the value or not

返回值类型:System.Boolean

如果元素被添加到 哈希集对象;如果元素已经存在,则返回 false。

因此您可以将代码范围缩小到:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-20
    • 2011-07-28
    • 2017-07-29
    • 2019-06-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多