如何创建集合的Lockfree集合
本文关键字:集合 Lockfree 创建 何创建 | 更新日期: 2023-09-27 18:11:26
我需要创建一个集合集合。该集合由多个线程调用,以添加项和查找项。一旦添加的项目将不会被删除。目前,在添加元素时,我需要对整个集合进行锁定。有没有办法让它不上锁。或者是否有更好的数据结构或模式可供我使用?下面是我的代码的简化版本:
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;
}
}
}
}
您可以通过使用不变性使哈希表无锁,但是如果存在争用,则可能效率不高。基本上,您需要一个可以自动交换的字典内容类。您构建当前内容的副本,只进行了一次更改,然后使用比较-交换原语将其与现有版本交换。如果比较交换失败,则重新开始复制步骤。
您可能能够自动交换单个哈希桶,这将使争用更少,并且重试更便宜。(ConcurrentDictionary
已经使用了这个优化,以减少锁争用)但是增加桶的数量仍然需要上面概述的方法。
看看Eric Lippert的博客,他在其中介绍了不可变数据结构。他有一个很好的二叉树示例,它应该向您展示创建无锁哈希表所需的技术。
方法ConcurrentDictionary.GetOrAdd
是线程安全的(尽管不是原子的)。它保证返回的对象对于所有线程都是相同的。您的代码可以重写为:
void AddUpdateItem(string s, int k, int v)
{
var subDict = dict.GetOrAdd(s, _ => new ConcurrentDictionary<int, int>());
subDict[k] = v;
}
您是否在代码中使用任务或线程?在任何情况下,ConcurrentDictionary
都被设计为线程安全的。在添加或删除元素时不需要使用锁。链接来自MSDN如何:从ConcurrentDictionary中添加和删除项解释了如何使用它。
如果您推测创建子字典,有一个更简单的解决方案:
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;
}
}
在开始实现无锁集合之前,请先了解一下可以解决问题的ReadWriteLock。如果不是这样(例如,因为有大量的写争用),就没有真正的放之四海而皆准的方法。
我过去使用的一种技术是使用一个专门用于管理集合的线程,并使用Interlocked.Exchange
将新对象编组到该线程,并将不可变集合发送出去。使用这种方法,您的写入器线程被管理在一个单独的列表中,当创建或销毁写入器时,您需要锁定该列表,因此这仅在罕见事件时有效。