ConcurrentDictionary 线程安全替代方法中的 AddOrUpdate 方法

AddOrUpdate method in ConcurrentDictionary thread safe alternative

根据此 link Is AddOrUpdate thread safe in ConcurrentDictionary? from the "Remarks" section on this page https://msdn.microsoft.com/en-us/library/dd287191(v=vs.110).aspx。它说

"However, delegates for these methods are called outside the locks to avoid the problems that can arise from executing unknown code under a lock. Therefore, the code executed by these delegates is not subject to the atomicity of the operation."

问题是我在我的代码中广泛使用了这个并且意识到我有一个潜在的非常讨厌的错误(我的理解一直是错误的)......因为在我使用下面的方式时我没想到代表们可以同时调用并覆盖内部字典:

internal class HandlerProducers
{
    readonly ConcurrentDictionary<Type, Dictionary<Type, InstanceProducer>> handlerProducers
        = new ConcurrentDictionary<Type, Dictionary<Type, InstanceProducer>>();

    public void AddProducer(Type notificationType, Type concreteType, InstanceProducer producer)
    {
        this.handlerProducers
                .AddOrUpdate(
                    notificationType,
                    (key) => new Dictionary<Type, InstanceProducer> { { concreteType, producer } },
                    (key, dictionary) => { dictionary.Add(concreteType, producer); return dictionary; });
    }

    public IEnumerable<InstanceProducer> GetForHandler(Type notificationHandlerType)
    {
        if(this.handlerProducers.TryGetValue(notificationHandlerType, out var dict))
        {
            foreach (var kvp in dict)
                yield return kvp.Value;
        }
    }
}

我有一个进一步的挑战,天真地将锁放在适当的位置可能会导致热点,上面的 class 广泛用于通过 GetForHandler() 读取并偶尔写入(通过AddProducer() 方法。

通过大量读取和偶尔写入的性能确保线程安全设计的最佳方法是什么?

我建议使用嵌套 ConcurrentDictionary 结构:

readonly ConcurrentDictionary<Type, ConcurrentDictionary<Type, InstanceProducer>> handlerProducers
    = new ConcurrentDictionary<Type, ConcurrentDictionary<Type, InstanceProducer>>();

然后您可以像这样实现 AddProducer 方法:

public void AddProducer(Type notificationType, Type concreteType,
    InstanceProducer producer)
{
    ConcurrentDictionary<Type, InstanceProducer> innerDict =
        this.handlerProducers.GetOrAdd(notificationType,
            _ => new ConcurrentDictionary<Type, InstanceProducer>());

    if (!innerDict.TryAdd(concreteType, producer))
        throw new InvalidOperationException(
            $"{notificationType}.{concreteType} already exists.");
}

方法GetForHandler不需要修改。

此实施将使您的 HandlerProducers class 真正 thread-safe,而不会牺牲其效率。