如何创建Lockfree集合集合

时间:2011-10-16 02:31:14

标签: c# multithreading thread-safety lock-free concurrent-programming

我需要创建一个集合集合。多个线程调用该集合以添加项和查找项。添加后,项目将不会被删除。目前,在添加元素时我需要锁定整个集合。有没有办法让它无锁。或者,我可以使用更好的数据结构或模式吗? 这是我的代码的简化版本:

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;
            }
        }
    }
}

5 个答案:

答案 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将新对象封送到该线程并将不可变集合输出。使用这种方法,您的编写器线程将在一个单独的列表中进行管理,无论何时创建或销毁编写器,您都需要锁定它,因此只有在这种情况很少的情况下才有效。