C# ConcurrentDictionary键或值属性是线程安全的吗
对使用C# ConcurrentDictionary键或值属性是线程安全的吗,c#,.net,multithreading,C#,.net,Multithreading,对使用ConcurrentDictionary的线程安全性有疑问。从API中,我看到枚举器是线程安全的,但我没有看到键和值属性是线程安全的。我的问题是: 当有其他线程同时修改集合时,在键或值集合上循环是否安全 ConcurrentDictionary表示线程安全的键值集合 可由多个线程同时访问的对 来源:虽然我确实喜欢这些文档,但当有疑问或者我觉得我可能假设得太多时,我倾向于用一个小程序来验证 下面的代码验证您确实可以安全地枚举values集合,同时将键从单独的线程添加或删除到正在进行枚举的线程
ConcurrentDictionary
的线程安全性有疑问。从API中,我看到枚举器是线程安全的,但我没有看到键和值属性是线程安全的。我的问题是:
当有其他线程同时修改集合时,在键
或值
集合上循环是否安全
ConcurrentDictionary表示线程安全的键值集合
可由多个线程同时访问的对
来源:虽然我确实喜欢这些文档,但当有疑问或者我觉得我可能假设得太多时,我倾向于用一个小程序来验证 下面的代码验证您确实可以安全地枚举values集合,同时将键从单独的线程添加或删除到正在进行枚举的线程。这不会导致通常的集合被修改异常。更详细地说,这里有几个测试用例 案例1:枚举值并删除键 如果按照以下顺序操作:
- 开始枚举线程中的值集合
- 从我们尚未枚举的其他线程中删除密钥
- 继续在原始线程上枚举
- 开始枚举线程中的值集合
- 从我们尚未枚举的另一个线程添加一个新密钥
- 继续在原始线程上枚举
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=新建ConcurrentDictionary();
//为字典添加一些任意值;
对于(int i=0;i<30;i++)
{
字典.TryAdd(i,i);
}
//读卡器线程-枚举值集合
Task.Factory.StartNew(
() =>
{
foreach(dictionary.Values中的变量项)
{
WriteLine(“项{0}:count:{1}”,项,dictionary.count);
睡眠(20);
}
}
);
//编写器线程-通过添加新项并从末尾删除现有项来修改字典
Task.Factory.StartNew(
() =>
{
对于(int i=29;i>=0;i--)
{
睡眠(10);
//删除现有条目
内移值;
if(dictionary.TryRemove(i,out removedValue))
WriteLine(“删除的项{0}”,removedValue);
其他的
WriteLine(“未删除项{0}”,i);
int-iVal=50+i*2;
字典[iVal]=iVal;
睡眠(10);
iVal++;
字典.TryAdd(iVal,iVal);
}
}
);
Console.ReadKey();
这是释放模式下的输出:
是的,它是线程安全的。但是,即使它是线程安全的,您也不希望使用键
,值
,以及计数
尤其是当您使用ConcurrentCollections
时,因为您希望最小化锁争用、线程阻塞和内存分配。如果你关心性能和效率,你确实想要这些东西
查看以了解为什么-Keys
会立即调用GetKeys()
帮助程序,并在继续之前获取每个锁。一旦有了锁,它会将每个密钥复制到一个新列表中,并返回该列表的只读视图,这样就不会有人意外地改变密钥集合的临时副本。这需要分配相当大的数组,如果您的集合很大,则需要在相当长的一段时间内保持锁
值
以类似于键的方式锁定和复制每个值
。即使是Count
也会获取所有锁,不是为了复制,而是为了汇总所有内部表段长度。所有这些都只是为了获得集合中对象的瞬时“一致”计数,这仅在锁释放后用作粗略估计或历史脚注
是的,叹息,如果你需要原子一致性,我想这可能是你必须付出的代价。但是也许你比那更幸运。然后,您可能会意识到,您的场景实际上并不需要一致性,而对于那些糟糕的API,性能更高的API就在您的掌握之中——比如使用GetEnumerator()
来大致了解集合中的项目!GetEnumerator()
文档中的备注:
从字典返回的枚举数可以安全使用
同时读取和写入字典,但是
不代表字典的即时快照。这个
通过枚举器公开的内容可能包含所做的修改
在调用GetEnumerator后将其添加到字典
换句话说,它根本不需要锁定或复制,因为它不需要确保一致性。万岁 +1,简明!:)添加:是的,这两个属性都受锁保护,您将枚举的是启动枚举时字典的内容(因此您可能会看到一个键,例如,它已从另一个线程中删除)。因此您需要