HashSet <t>线程作为ConcurrentDictionary <tkey,hashset <t =“” >>的值是否安全?

时间:2018-11-20 00:16:37

标签: c# multithreading concurrentdictionary

如果我有以下代码:

var dictionary = new ConcurrentDictionary<int, HashSet<string>>();

foreach (var user in users)
{
   if (!dictionary.ContainsKey(user.GroupId))
   {
       dictionary.TryAdd(user.GroupId, new HashSet<string>());
   }

   dictionary[user.GroupId].Add(user.Id.ToString());
}

因为HashSet是并发字典的value属性,所以将项目添加到HashSet本质上是线程安全的吗?

3 个答案:

答案 0 :(得分:5)

不。将容器放入线程安全的容器中不会使内部容器的线程安全。

dictionary[user.GroupId].Add(user.Id.ToString());

在从ConcurrentDictionary中检索HashSet的添加后调用它。如果一次从两个线程中查找此GroupId,则可能会以奇怪的失败模式破坏代码。我看到我的一个队友犯了一个错误,即没有锁定他的布景,而且效果不佳。

这是一个可行的解决方案。我本人会做一些不同的事情,但这更接近您的代码。

if (!dictionary.ContainsKey(user.GroupId)
{
    dictionary.TryAdd(user.GroupId, new HashSet<string>());
}
var groups = dictionary[user.GroupId];
lock(groups)
{
    groups.Add(user.Id.ToString())
}

答案 1 :(得分:4)

不,集合(字典本身)是线程安全的,而不管您放入其中的内容如何。您有两种选择:

  1. 使用AddOrUpdate作为@TheGeneral提到的内容:

    dictionary.AddOrUpdate(user.GroupId,  new HashSet<string>(), (k,v) => v.Add(user.Id.ToString());
    
  2. 使用并发集合,例如ConcurrentBag<T>

    ConcurrentDictionary<int, ConcurrentBag<string>>
    

无论何时构建词典(如代码中的内容),最好都尽可能少地访问它。想像这样的东西:

var dictionary = new ConcurrentDictionary<int, ConcurrentBag<string>>();
var grouppedUsers = users.GroupBy(u => u.GroupId);

foreach (var group in grouppedUsers)
{
    // get the bag from the dictionary or create it if it doesn't exist
    var currentBag = dictionary.GetOrAdd(group.Key, new ConcurrentBag<string>());

    // load it with the users required
    foreach (var user in group)
    {
        if (!currentBag.Contains(user.Id.ToString())
        {
            currentBag.Add(user.Id.ToString());
        }
    }
}
  1. 如果您实际上想要一个内置的并发类似HashSet的集合,则需要使用ConcurrentDictionary<int, ConcurrentDictionary<string, string>>,并关心密钥或内部密钥的值。

答案 2 :(得分:1)

像这样使用ConcurrentDictionary并不是线程安全的

dictionary[user.GroupId].Add(user.Id.ToString());

代替使用

AddOrUpdate(TKey, TValue, Func)

dictionary.AddOrUpdate(user.GroupId,  new HashSet<string>(), (k,v) => v.Add(user.Id.ToString());

或者如 Camilo Terevinto 所说,ConcurrentBag可能是您想要的地方