Для .NET 3.5 как бы вы сделали потокобезопасным получение или добавление кеша? - PullRequest
8 голосов
/ 17 декабря 2010

Я начал работать с некоторым кодом .NET 3.5 и обнаружил, что для кэша используется следующий метод расширения:

public static TValue GetOrAdd<TKey, TValue>(this Dictionary<TKey, TValue> @this, TKey key,Func<TKey,TValue> factory,bool useLocking)
{

    TValue value;
    if(!@this.TryGetValue(key,out value))
    {
        if (useLocking)
        {
            lock ((@this as ICollection).SyncRoot)
            {
                if (!@this.TryGetValue(key, out value))
                {
                    @this[key] = value = factory(key);
                }
            }
        }
        else
        {
            @this[key] = value = factory(key);
        }
    }
    return value;
}

Кэш, о котором идет речь, имеет строковые ключи и использует useLocking = true. К этому методу всегда обращаются (нет никаких помех TryGetValue). Также нет проблем с использованием свойства SyncRoot, так как словарь является частным и нигде не используется. Двойная блокировка опасна, потому что словарь не поддерживает чтение во время записи. Хотя технически об этой проблеме еще не сообщалось, поскольку продукт не поставлялся, я чувствую, что такой подход приведет к гоночным условиям.

  1. Переключите Dictionary<,> на Hashtable. Мы потеряем безопасность типов, но мы сможем поддерживать модель параллелизма, которая нам нужна (1 писатель, несколько читателей).

  2. Удалите внешнее TryGetValue. Таким образом, каждое чтение требует блокировки. Это потенциально плохо сказывается на производительности, но приобретает неоспоримое замок должен быть довольно дешево.

Оба довольно дерьмовые. У кого-нибудь есть лучшее предложение? Если бы это был код .NET 4, я бы просто переключил его на ConcurrentDictionary, но у меня нет такой опции.

Ответы [ 4 ]

2 голосов
/ 31 декабря 2010

Да, ваши подозрения верны; существует условие гонки, и все методы, даже TryGetValue, должны находиться внутри блокировки в представленной вами реализации.

Что касается производительности, вы можете рассчитывать на тот день, когда вы сможете перейти на .NET4, который включает в себя быстрое ConcurrentDictionary из коробки. До этого вы можете просмотреть анализ Джеймса Майкла Хэра, сделанный здесь:

Эти результаты говорят мне о том, что лучшая реализация для .NET3.5 - это Dictionary плюс ReadWriteLockSlim, и для хорошей меры, вот полная реализация:

Обновление:

Я неправильно прочитал таблицы, и похоже, что Dictionary + lock немного быстрее, чем единственный серьезный соперник Dictionary + ReadWriteLockSlim.

1 голос
/ 31 декабря 2010

Я рекомендую загрузить Reactive Extensions для .NET (Rx) , который включает бэкпорты коллекций в пространстве имен System.Collections.Concurrent (включая ConcurrentDictionary<TKey, TValue>) для .NET 3.5.

0 голосов
/ 30 декабря 2010

Я думаю, вам нужно заблокировать полный оператор (как вы заявили).

Однако в Интернете есть удобное решение для подготовки к будущему обновлению .NET 4: используйте собственный словарь вместо словаря, сохраните словарь в качестве закрытой переменной-члена и оберните весь доступ к словарю в потоке. Заявление о безопасном замке. Подробное описание можно найти здесь: http://www.grumpydev.com/2010/02/25/thread-safe-dictionarytkeytvalue/

0 голосов
/ 27 декабря 2010

Удалить TryGetValue.Бьюсь об заклад, вы не увидите проблемы параллелизма;Мониторы CLR довольно быстрые и «несправедливые», так что вы не видите проблем с конвоями или инверсией приоритетов.

Если вы видите проблему параллелизма, то следующая лучшая вещь - ReaderWriterLockSlim.К сожалению, вы захотите создать для этого новый класс вместо использования метода расширения, потому что вам понадобится место для хранения блокировки.

Если вы идете по этому пути, обязательнодержаться подальше от перехода с читательских на писательские замки.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...