线程安全的订阅列表的最佳数据结构?

时间:2012-01-26 17:12:04

标签: c# data-structures collections thread-safety

我正在尝试构建订阅列表。我们举个例子:

发布商列表,每个发布商都有一个杂志列表,每个杂志都有一个订阅者列表

发布商 - >杂志 - >订户

在C#中的词典中使用词典中的词典是有意义的。在没有竞争条件的情况下添加/删除用户时,是否可以在不锁定整个结构的情况下执行此操作?

此外,代码在C#中变得非常混乱,这让我觉得我没有走正确的道路。有更简单的方法吗?以下是构造函数和订阅方法:

注意:代码使用Source,Type,Subscriber而不是上面的名称

来源--->类型--->订户

public class SubscriptionCollection<SourceT, TypeT, SubscriberT>
{
// Race conditions here I'm sure! Not locking anything yet but should revisit at some point

ConcurrentDictionary<SourceT, ConcurrentDictionary<TypeT, ConcurrentDictionary<SubscriberT, SubscriptionInfo>>> SourceTypeSubs;

public SubscriptionCollection()
{
    SourceTypeSubs = new ConcurrentDictionary<SourceT, ConcurrentDictionary<TypeT, ConcurrentDictionary<SubscriberT, SubscriptionInfo>>>();
}

public void Subscribe(SourceT sourceT, TypeT typeT, SubscriberT subT) {

    ConcurrentDictionary<TypeT, ConcurrentDictionary<SubscriberT, SubscriptionInfo>> typesANDsubs;
    if (SourceTypeSubs.TryGetValue(sourceT, out typesANDsubs))
    {
        ConcurrentDictionary<SubscriberT, SubscriptionInfo> subs;
        if (typesANDsubs.TryGetValue(typeT, out subs))
        {

            SubscriptionInfo subInfo;
            if (subs.TryGetValue(subT, out subInfo))
            {
                // Subscription already exists - do nothing

            }
            else
            {
                subs.TryAdd(subT, new SubscriptionInfo());
            }
        }
        else
        {
            // This type does not exist - first add type, then subscription
            var newType = new ConcurrentDictionary<SubscriberT, SubscriptionInfo>();
            newType.TryAdd(subT, new SubscriptionInfo());
            typesANDsubs.TryAdd(typeT, newType);

        }

    }
    else
    {
        // this source does not exist - first add source, then type, then subscriptions
        var newSource = new ConcurrentDictionary<TypeT, ConcurrentDictionary<SubscriberT, SubscriptionInfo>>();
        var newType = new ConcurrentDictionary<SubscriberT, SubscriptionInfo>();
        newType.TryAdd(subT, new SubscriptionInfo());
        newSource.TryAdd(typeT, newType);
        SourceTypeSubs.TryAdd(sourceT, newSource);
    };
}

1 个答案:

答案 0 :(得分:2)

如果你使用ConcurrentDictionary,就像你已经做的那样,你不需要锁定,已经处理好了。

但你仍然需要考虑竞争条件以及如何处理它们。幸运的是,ConcurrentDictionary可以为您提供所需的内容。例如,如果您有两个线程,它们都尝试同时订阅尚不存在的源,则只有其中一个线程会成功。但这就是TryAdd()返回加法是否成功的原因。你不能只忽略它的返回值。如果它返回false,你知道其他一些线程已经添加了该源,所以你现在可以检索字典。

另一种选择是使用the GetOrAdd() method。它检索已存在的值,如果它还不存在则创建它。

我会像这样重写你的代码(并且在此过程中使其变得更简单):

public void Subscribe(SourceT sourceT, TypeT typeT, SubscriberT subT)
{
    var typesAndSubs = SourceTypeSubs.GetOrAdd(sourceT,
        _ => new ConcurrentDictionary<TypeT, ConcurrentDictionary<SubscriberT, SubscriptionInfo>>());

    var subs = typesAndSubs.GetOrAdd(typeT,
        _ => new ConcurrentDictionary<SubscriberT, SubscriptionInfo>());

    subs.GetOrAdd(subT, _ => new SubscriptionInfo());
}