我需要创建一个集合集合。多个线程调用该集合以添加项和查找项。添加后,项目将不会被删除。目前,在添加元素时我需要锁定整个集合。有没有办法让它无锁。或者,我可以使用更好的数据结构或模式吗? 这是我的代码的简化版本:
readonly ConcurrentDictionary<string, ConcurrentDictionary<int, int>> dict = new ConcurrentDictionary<string, ConcurrentDictionary<int, int>>();
void AddUpdateItem(string s, int k, int v)
{
ConcurrentDictionary<int, int> subDict;
if (dict.TryGetValue(s, out subDict))
{
subDict[k] = v;
}
else
{
lock (dict)
{
if (dict.TryGetValue(s, out subDict))
{
subDict[k] = v;
}
else
{
subDict = new ConcurrentDictionary<int, int>();
subDict[k] = v;
dict[s] = subDict;
}
}
}
}
答案 0 :(得分:4)
您可以通过使用不变性来使哈希表无锁,但如果存在争用则不太可能有效。基本上,您需要一个可以原子交换的字典内容类。您构建了当前内容的副本,并进行了一次更改,然后使用比较和交换原语将其与现有版本进行交换。如果比较和交换失败,请从复制步骤开始。
您可能只能原子地交换一个哈希桶,这会使争用更不常见,并且重试更便宜。 (ConcurrentDictionary
已经使用此优化来减少锁争用)但是增加桶的数量仍然需要上面概述的方法。
看看Eric Lippert的博客,其中涵盖了不可变数据结构。他有a nice example of a binary tree,它应该向您展示制作无锁散列表所需的技术。
答案 1 :(得分:3)
方法ConcurrentDictionary.GetOrAdd
是线程安全的(尽管不是原子的)。它保证返回的对象对于所有线程都是相同的。您的代码可以重写为:
void AddUpdateItem(string s, int k, int v)
{
var subDict = dict.GetOrAdd(s, _ => new ConcurrentDictionary<int, int>());
subDict[k] = v;
}
答案 2 :(得分:1)
您是否在代码中使用任务或线程?无论如何,ConcurrentDictionary
被设计为线程安全的。添加或删除元素时不需要使用锁。来自MSDN How to: Add and Remove Items from a ConcurrentDictionary的链接说明了如何使用它。
答案 3 :(得分:0)
如果您推测性地创建子词典,则有一个更简单的解决方案:
readonly ConcurrentDictionary<string, ConcurrentDictionary<int, int>> dict = new ConcurrentDictionary<string, ConcurrentDictionary<int, int>>();
void AddUpdateItem( string s, int k, int v )
{
ConcurrentDictionary<int, int> subDict;
while ( true )
{
if ( dict.TryGetValue( s, out subDict ) )
{
subDict[ k ] = v;
break;
}
// speculatively create new sub-dictionary
subDict = new ConcurrentDictionary<int, int>();
subDict[ k ] = v;
// this can only succeed for one thread
if ( dict.TryAdd( s, subDict ) ) break;
}
}
答案 4 :(得分:0)
在您实施无锁集合之前,请先查看ReadWriteLock来解决您的问题。如果没有(例如因为你有大量的写入争用),那么实际上并没有一种通用的方法。
我过去使用过的一种技术是使用线程专用线程来管理集合,并使用Interlocked.Exchange
将新对象封送到该线程并将不可变集合输出。使用这种方法,您的编写器线程将在一个单独的列表中进行管理,无论何时创建或销毁编写器,您都需要锁定它,因此只有在这种情况很少的情况下才有效。