简体   繁体   English

如何创建Lockfree集合集合

[英]How to create a Lockfree collection of collection

I need to create a collection of collections. 我需要创建一个集合集合。 The collection is called by multiple threads to add items and lookup items. 多个线程调用该集合以添加项和查找项。 Once added the items will not be removed. 添加后,项目将不会被删除。 Currently, while adding elements I need to take a lock on the entire collection. 目前,在添加元素时我需要锁定整个集合。 Is there a way around it to make it lockfree. 有没有办法让它无锁。 Or is there a better datastructure or pattern that I can use? 或者,我可以使用更好的数据结构或模式吗? Here is a simplified version of my code: 这是我的代码的简化版本:

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

You can make a hashtable lock-free, by using immutability, but it isn't likely to be efficient if there's contention. 您可以通过使用不变性来使哈希表无锁,但如果存在争用则不太可能有效。 Basically, you need a dictionary-content class that can be atomically swapped. 基本上,您需要一个可以原子交换的字典内容类。 You build a copy of the current-content, with the one change made, then use a compare-and-swap primitive to exchange it with the existing version. 您构建了当前内容的副本,并进行了一次更改,然后使用比较和交换原语将其与现有版本进行交换。 If compare-and-swap fails, start over with the copying step. 如果比较和交换失败,请从复制步骤开始。

You might be able to atomically swap just a single hash-bucket, which would make contention much less common, and retry cheaper. 您可以原子地交换一个哈希桶,这会使争用更不常见,并重试更便宜。 ( ConcurrentDictionary does already use this optimization, to reduce lock contention) But growing the number of buckets will still require the method outlined above. ConcurrentDictionary已经使用了这种优化,以减少锁争用)但是增加桶的数量仍然需要上面概述的方法。

Have a look at Eric Lippert's blog, in which he covers immutable data structures. 看看Eric Lippert的博客,其中涵盖了不可变数据结构。 He's got a nice example of a binary tree , which should show you the techniques needed to make a lockless hash-table. 他有一个很好的二叉树示例 ,它应该向您展示制作无锁散列表所需的技术。

Method ConcurrentDictionary.GetOrAdd is thread safe (although not atomic). 方法ConcurrentDictionary.GetOrAdd是线程安全的(虽然不是原子的)。 It guarantees that the returned object is the same for all threads. 它保证返回的对象对于所有线程都是相同的。 Your code could be rewritten as: 您的代码可以重写为:

void AddUpdateItem(string s, int k, int v)
{
    var subDict = dict.GetOrAdd(s, _ => new ConcurrentDictionary<int, int>());
    subDict[k] = v;
}

Are you using tasks or threads in your code ? 您是否在代码中使用任务或线程? In any case, ConcurrentDictionary is designed to be thread-safe. 无论如何, ConcurrentDictionary被设计为线程安全的。 You don't need to use locks while add or remove elements. 添加或删除元素时不需要使用锁。 Link from MSDN How to: Add and Remove Items from a ConcurrentDictionary explains how to use it. 从MSDN链接如何:在ConcurrentDictionary中添加和删除项目解释了如何使用它。

If you speculatively create the sub-dictionary, there is a simpler solution: 如果您推测性地创建子字典,则有一个更简单的解决方案:

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

before you go down the road of implementing a lockless collection have a look at ReadWriteLock which solve your problem. 在你实现无锁集合之前,先看看ReadWriteLock来解决你的问题。 If it doesn't (eg because you have heavy write contention) there isn't really a one-size-fits-all approach. 如果没有(例如因为你有很大的写入争用),那么实际上并没有一种通用的方法。

One technique I ave used in the past is to have a thread dedicated thread to managing the collection and use Interlocked.Exchange to marshal new objects to that thread and an immutable collection out. 我过去使用的一种技术是使用一个线程专用线程来管理集合,并使用Interlocked.Exchange将新对象封送到该线程和一个不可变集合。 With this approach your writer threads are managed in a separate list that you need to lock whenever a writer is created or destroyed, so this only works if that is a rare event. 使用这种方法,您的编写器线程将在一个单独的列表中进行管理,无论何时创建或销毁编写器,您都需要锁定它,因此只有在这是一个罕见的事件时才有效。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM