作为 ConcurrentDictionary<TKey, HashSet<T>> 的值,HashSet<T> 线程安全吗?
Is HashSet<T> thread safe as a value of ConcurrentDictionary<TKey, HashSet<T>>?
如果我有以下代码:
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 是并发字典的值 属性,所以将项目添加到 HashSet 的行为本质上是线程安全的吗?
没有。将容器放入线程安全容器中并不能使内部容器线程安全。
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());
}
不,集合(字典本身)是线程安全的,而不是您放入其中的任何内容。您有几个选择:
如@TheGeneral 所述使用AddOrUpdate
:
dictionary.AddOrUpdate(user.GroupId, new HashSet<string>(), (k,v) => v.Add(user.Id.ToString());
使用并发集合,如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());
}
}
}
- 如果你真的想要一个内置的并发 HashSet 类集合,你需要使用
ConcurrentDictionary<int, ConcurrentDictionary<string, string>>
,并关心内部的键或值。
如果我有以下代码:
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 是并发字典的值 属性,所以将项目添加到 HashSet 的行为本质上是线程安全的吗?
没有。将容器放入线程安全容器中并不能使内部容器线程安全。
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());
}
不,集合(字典本身)是线程安全的,而不是您放入其中的任何内容。您有几个选择:
如@TheGeneral 所述使用
AddOrUpdate
:dictionary.AddOrUpdate(user.GroupId, new HashSet<string>(), (k,v) => v.Add(user.Id.ToString());
使用并发集合,如
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());
}
}
}
- 如果你真的想要一个内置的并发 HashSet 类集合,你需要使用
ConcurrentDictionary<int, ConcurrentDictionary<string, string>>
,并关心内部的键或值。