![](/img/trans.png)
[英]Is a linq query to ConcurrentDictionary Values threadsafe?
[英]Is ConcurrentDictionary Keys or Values property threadsafe
对ConcurrentDictionary
线程安全有疑问。 从 API 中,我看到枚举器是线程安全的,但对于键和值属性,我没有看到相同的内容。 我的问题是:
当有其他线程同时修改它时,循环遍历Keys
或Values
集合是否安全?
虽然我确实喜欢文档,但在有疑问或我觉得我可能假设太多时,我倾向于用一个小程序来验证。
以下代码验证您确实可以安全地枚举值集合,同时从单独的线程添加或删除键到正在发生枚举的线程。 这不会导致通常的集合被修改异常。 更详细地说,这里有几个测试用例
案例 1:枚举值并删除一个键
如果您遵循以下顺序:
观察到的行为是删除的键确实会被枚举,因为它在我们开始枚举时存在于值集合中。 不会引发任何异常。
案例 2:枚举值并添加键
观察到的行为是添加的键不会被枚举,因为当我们开始枚举它时,它不存在于值集合中。 无论我们使用 TryAdd 还是通过直接分配给字典(即 dictionary[key] = value)来添加都不会引发异常。
示例代码
这是演示这两种情况的示例程序:
ConcurrentDictionary<int, int> dictionary = new ConcurrentDictionary<int, int>();
// Seed the dictionary with some arbitrary values;
for (int i = 0; i < 30; i++)
{
dictionary.TryAdd(i, i);
}
// Reader thread - Enumerate the Values collection
Task.Factory.StartNew(
() =>
{
foreach (var item in dictionary.Values)
{
Console.WriteLine("Item {0}: count: {1}", item, dictionary.Count);
Thread.Sleep(20);
}
}
);
// writer thread - Modify dictionary by adding new items and removing existing ones from the end
Task.Factory.StartNew(
() =>
{
for (int i = 29; i >= 0; i--)
{
Thread.Sleep(10);
//Remove an existing entry
int removedValue;
if (dictionary.TryRemove(i, out removedValue))
Console.WriteLine("Removed item {0}", removedValue);
else
Console.WriteLine("Did not remove item {0}", i);
int iVal = 50 + i*2;
dictionary[iVal] = iVal;
Thread.Sleep(10);
iVal++;
dictionary.TryAdd(iVal, iVal);
}
}
);
Console.ReadKey();
这是发布模式下的输出:
ConcurrentDictionary 表示可以被多个线程并发访问的键值对的线程安全集合。
来源: MSDN
是的,它的线程安全。 但是,即使它是线程安全的,您也不应该使用Keys
、 Values
和Count
。
当您使用ConcurrentCollections<T>
时尤其如此,因为您希望最大限度地减少锁争用、线程阻塞和内存分配。 如果您关心性能和效率,您确实需要这些东西。
查看参考源以了解原因 - Keys
立即调用GetKeys()
帮助程序,并在继续之前获取每个锁。 一旦有了锁,它就会将每个键复制到一个new List<TKey>
,并返回一个只读视图——这样就不会有人意外地改变实际上只是键集合的临时副本的东西。 这需要分配相当大的数组,并持有锁很长一段时间,如果你的集合变大了!
Values
类似于Keys
锁定和复制每个单独的值。 甚至Count
获取所有锁,不是为了复制,是为了求和所有内部表段长度。 所有这一切只是为了获得集合中对象的暂时“一致”计数,一旦锁定被释放,这仅作为粗略估计或历史脚注有用。
所以是的,叹息,如果你需要原子一致性,我想这可能是你必须付出的代价。 但! 也许你比那更幸运。 然后,您可能会意识到您的场景实际上并不需要一致性,并且您可以掌握针对那些不良 API 的性能更高的 API - 例如使用GetEnumerator()
来大致了解您的集合中有哪些项目! GetEnumerator()
文档中的注释:
从字典返回的枚举器可以安全地与对字典的读取和写入同时使用,但它不代表字典的即时快照。 通过枚举器公开的内容可能包含在调用 GetEnumerator 后对字典所做的修改。
换句话说,它根本不需要锁定或复制,因为它不需要确保一致性。 万岁!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.