【问题标题】:Best data structure for thread-safe list of subscriptions?订阅线程安全列表的最佳数据结构?
【发布时间】:2012-02-19 18:55:55
【问题描述】:

我正在尝试建立一个订阅列表。举个例子吧:

出版商列表,每个都有一个杂志列表,每个都有一个订阅者列表

出版商 --> 杂志 --> 订阅者

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

此外,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);
    };
}

【问题讨论】:

  • 这个问题是 C# 特有的,还是您正在寻找一种可以在任何地方使用的方法?
  • 真的在任何地方.. 然后我可以将它改编为 C#
  • 我之所以问,是因为如果问题是特定于 C# 的,那么您可以直接在 .Net 框架中使用类。
  • 好吧,我想推荐ConcurrentDictionary,但刚才注意到你已经在使用它了。

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


【解决方案1】:

如果您使用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());
}

【讨论】:

  • GetOrAdd 确实使代码更简洁,因为我不必检查 TryAdd 的返回值并再次尝试(可能多次?)以确保每个线程最终成功通过。如果没有其他结果,现在不时投票将选择作为接受的答案。非常感谢!!
  • @HarryMexican,您不必多次执行TryAdd()。只有当其他人已经添加了具有相同密钥的项目时,它才会失败。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-26
相关资源
最近更新 更多