Лучшая структура данных для потокобезопасного списка подписок? - PullRequest
1 голос
/ 26 января 2012

Я пытаюсь создать список подписки. Давайте возьмем пример:

список издателей, каждый из которых имеет список журналов, каждый из которых имеет список подписчиков

Издательства -> Журналы -> Подписчики

Имеет смысл использовать словарь в словаре в словаре в C #. Возможно ли это сделать без блокировки всей структуры при добавлении / удалении абонента без условий гонки?

Также код очень быстро запутывается в C #, что заставляет меня думать, что я не иду по правильному пути. Есть ли более простой способ сделать это? Вот конструктор и метод подписки:

Примечание. Код использует Источник, Тип, Подписчик вместо названий выше

Источник ---> Тип ---> Подписчик

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 Ответ

2 голосов
/ 27 января 2012

Если вы используете 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());
}
...