Как сделать поток реализации моего хеш-таблицы безопасным, когда он очищен? - PullRequest
2 голосов
/ 12 октября 2009

У меня есть класс, используемый для кэширования доступа к ресурсу базы данных. Это выглядит примерно так:

//gets registered as a singleton
class DataCacher<T>
{
    IDictionary<string, T> items = GetDataFromDb();

    //Get is called all the time from zillions of threads
    internal T Get(string key)
    {
         return items[key];
    }

    IDictionary<string, T> GetDataFromDb() { ...expensive slow SQL access... }

    //this gets called every 5 minutes
    internal void Reset()
    {
          items.Clear();
    }
}

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

Теперь я могу просто добавить блокировку блоков в Get и Reset, но я обеспокоен тем, что блокировки в Get снизят производительность сайта, так как Get вызывается каждым потоком запросов в веб-приложении много раз.

Я могу сделать что-то с дважды проверенными блокировками, я думаю, но я подозреваю, что есть более чистый способ сделать это, используя что-то более умное, чем блок lock {}. Что делать?

edit: Извините, я не делал этого раньше, но реализация items.Clear (), которую я использую, на самом деле не является прямым словарем. Это оболочка ResourceProvider, которая требует, чтобы реализация словаря вызывала .ReleaseAllResources () для каждого из элементов по мере их удаления. Это означает, что вызывающий код не хочет работать со старой версией, которая находится в процессе утилизации. Учитывая это, метод Interlocked.Exchange является правильным?

Ответы [ 3 ]

6 голосов
/ 12 октября 2009

Я бы запустил , протестировав его только с lock; замки очень дешевы, когда не оспариваются. Однако более простая схема заключается в том, чтобы полагаться на атомарную природу справочных обновлений:

public void Clear() {
    var tmp = GetDataFromDb(); // or new Dictionary<...> for an empty one
    items = tmp; // this is atomic; subsequent get/set will use this one
}

Возможно, вы также захотите сделать поле items volatile, просто чтобы убедиться, что оно нигде не хранится в регистрах.

По-прежнему существует проблема, заключающаяся в том, что любой , ожидающий наличия данного ключа, может разочароваться (через исключение), но это отдельная проблема.

Более детальным вариантом может быть ReaderWriterLockSlim.

2 голосов
/ 12 октября 2009

Один из вариантов - полностью заменить экземпляр IDictionary вместо его очистки. Вы можете сделать это потокобезопасным способом, используя метод Exchange в классе Interlocked .

0 голосов
/ 12 октября 2009

Посмотрите, скажет ли база данных, какие данные изменились. Вы можете использовать

  • Триггер для записи изменений в таблицу истории
  • Уведомления о запросах (они есть у SqlServer и Oracle, другие тоже должны это делать)
  • Etc

Так что вам не нужно перезагружать все данные на основе таймера.


В противном случае.

Я бы заставил метод Clear создать новый IDictionary , вызвав GetDataFromDB () , затем после загрузки данных установите поле «items» указать на новый словарь. (Сборщик мусора очистит старый словарь, как только потоки не получат к нему доступ.)

Не думаю, что тебя волнует, если какие-то темы получить «старые» результаты при перезагрузке данные - (если вы это сделаете, то вы просто должны заблокировать все потоки на замок - больно!)

Если вам необходимо все потоки для одновременного переключения на новый словарь, тогда вам нужно объявить поле "items" volatile и использовать Обмен метод в классе Interlocked . Однако вряд ли вам это понадобится в реальной жизни.

...