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

发布于 2024-12-29 16:39:40 字数 2374 浏览 0 评论 0原文

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

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

杂志 都有一个订阅者列表杂志 -->订阅者

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

而且 C# 中的代码很快就会变得混乱,这让我觉得我没有走正确的道路。有没有更简单的方法来做到这一点?下面是构造函数和 subscribe 方法:

名称

注意:代码中使用了 Source、Type、Subscriber,而不是上面的Source ---> 。类型--->订户

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

I am trying to build a subscription list. Let's take the example:

list of Publishers, each having a list of Magazines, each having a list of subscribers

Publishers --> Magazines --> Subscribers

Makes sense to use of a Dictionary within a Dictionary within a Dictionary in C#. Is it possible to do this without locking the entire structure when adding/removing a subscriber without race conditions?

Also the code gets messy very quickly in C# which makes me think I am not going down the right path. Is there an easier way to do this? Here are the constructor and subscribe method:

Note: The code uses Source, Type, Subscriber instead of the names above

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

七月上 2025-01-05 16:39:40

如果您使用 ConcurrentDictionary(就像您已经做的那样),则不需要锁定,这已经得到解决。

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

另一种选择是使用 GetOrAdd() 方法。它检索已经存在的值,如果尚不存在则创建它。

我会像这样重写你的代码(并使其变得更简单):

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

If you use ConcurrentDictionary, like you already do, you don't need locking, that's already taken care of.

But you still have to think about race conditions and how to deal with them. Fortunately, ConcurrentDictionary gives you exactly what you need. For example, if you have two threads, that both try to subscribe to source that doesn't exist yet at the same time, only one of them will succeed. But that's why TryAdd() returns whether the addition was successful. You can't just ignore its return value. If it returns false, you know some other thread already added that source, so you can retrieve the dictionary now.

Another option is to use the GetOrAdd() method. It retrieves already existing value, and creates it if it doesn't exist yet.

I would rewrite your code like this (and make it much simpler along the way):

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());
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文