2015-03-21 3 views
-1

Я хотел бы получить совет относительно того, где я могу улучшить или изменить дизайн текущего кода.Словарь кэшированных данных с помощью ReaderWriterLockSlim C#

Существует класс Manager, который настроен и имеет метод вычисления, который вызывается различными потоками. Каждый поток имеет один ресурс, который ему нужно вычислить. Эти ресурсы могут принадлежать различным компаниям. Для каждой компании мы хотим кэшировать некоторые данные, но мы можем получать данные компании только через ресурс, когда вызывается метод Calculate.

Итак, моя идея в настоящее время состоит в том, чтобы иметь словарь в классе Manager с ключом компанииResourceTag в качестве ключа. Когда вызывается Calculate, определяется companyResourceTag и вызывается метод CheckCachedData.

private void CheckCachedData(int companyResourceTag) 
    { 
     _ReaderWriterLock.EnterUpgradeableReadLock(); 
     try 
     { 
      if (!_CompanyCachedData.ContainsKey(companyResourceTag)) 
      {     
        Elements elements = ElementService.GetAllElements(); 
        DataElements dataElements = ElementService.GetAllDataElements(); 
        CalendarGroupings calendarGroupings = CalendarService.GetAllCalendarGroupings();      

        CachedDataContainer cachedItem = new CachedDataContainer(elements, dataElements, calendarGroupings); 

        _ReaderWriterLock.EnterWriteLock(); 
        try 
        { 
         _CompanyCachedData.Add(companyResourceTag, cachedItem); 
        } 
        finally 
        { 
         _ReaderWriterLock.ExitWriteLock(); 
        } 
      } 
     } 
     finally 
     { 
      _ReaderWriterLock.ExitUpgradeableReadLock(); 
     } 
    } 

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

Может быть сказано 100 различных компаний и 30000+ ресурсов, которые должны быть рассчитаны. Есть несколько других мест (на ресурс), где этот кэшировать данные считываются из, например:

 _ReaderWriterLock.EnterReadLock(); 
     try 
     { 
      _CompanyCachedData.TryGetValue(companyResourceTag, out cachedDataContainer); 
     } 
     finally 
     { 
      _ReaderWriterLock.ExitReadLock(); 
     } 

     //Do something with cachedDataContainer 

Я не пытался сделать код более элегантным из-за комментарий Eric Lathrop здесь: Possible problem

я не использовал ConcurrentDictionary, из-за вопроса, упомянутого здесь: Possible problem with ConcurrentDictionary

Я не уверен, будет ли нормальные замки лучше, чем ReaderWriterLockSlim, но мне нравится идея, что может быть более чем один читатель в и я могу обновить блокировку. В настоящее время я также больше обеспокоен правильностью, чем скоростью.

Правильно ли я использовал RWLS? Согласны ли вы с моим текущим использованием UpgradeableReadLock? Согласны ли вы с моим выбором не использовать ConcurrentDictionary?

+0

«Если бы я хотел улучшить скорость» - то есть, что? При отсутствии четко выраженной наблюдаемой проблемы с производительностью наряду с четко сформулированной целью работы этот вопрос слишком широк. Какие у вас есть доказательства того, что код, который у вас есть сейчас, еще не очень быстрый? Учитывая, что у вас всегда есть вероятность, что вам придется получать данные по сети, и что эта задержка приемлема, трудно поверить, что даже один простой замок вызовет любую реальную проблему. Если RWLS работает, какой конкретный вопрос вы имеете здесь? –

+0

@Peter Duniho: Я отредактировал оригинальный пост, чтобы быть более конкретным. – Igavshne

ответ

2

Отсутствует a good, minimal, complete code example невозможно однозначно сказать о коде. Тем не менее, код, который вы опубликовали, выглядит в порядке с точки зрения правильности.

Но & hellip;

Согласны ли вы с моим текущим использованием UpgradeableReadLock?

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

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

(Примечание: приведенным выше, я просто имею в виду API, который видит вызывающий.Разумеется, имеет смысл, что фактическая реализация кэша приведет к разрыву фактической выборки данных в отдельный метод. Просто я ожидаю увидеть тот метод, который вызывается тем же методом, который использовался для извлечения значений из кеша, так что вам нужно только взять блокировку чтения один раз в том случае, если это значение уже находится в кеше).

Согласны ли вы с моим выбором не использовать ConcurrentDictionary?

Снова, не имея больше контекста, трудно сказать. Я стараюсь избегать ConcurrentDictionary, потому что он имеет несколько более сложную в использовании семантику по сравнению с обычным классом Dictionary<TKey, TValue>, по крайней мере, используя преимущества своих специфичных для параллелизма функций.

Но в вашем случае я бы подумал, что эти функции, связанные с параллелизмом, могут быть тем, что вы ищете. В частности, метод TValue GetOrAdd(TKey key, Func<TKey, TValue> valueFactory) реализует семантику, к которой обычно требуется кеш: извлекать существующее значение или заполнять словарь новым значением, если ключ отсутствует.

С другой стороны, существует важное предостережение с этим методом: делегат valueFactory может быть вызван несколько раз, если метод вызывается одновременно из нескольких потоков. Только один из результатов будет фактически использоваться для заполнения словаря, и он может иметь или не иметь локального влияния на производительность, но это, очевидно, может повлиять на общую нагрузку на сервер, из которого вы извлекаете данные.

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

С другой стороны, я буду отмечать свой предыдущий комментарий относительно относительной производительности здесь. То есть, неясно, что потенциальное преимущество производительности ReaderWriterLockSlim действительно важно здесь. Теоретически, в уже заполненном случае должно быть меньше накладных расходов по сравнению с использованием класса Monitor (т. Е. lock), но я бы подумал, что вам придется иметь дело с очень высоким уровнем конкуренции, чтобы играть на самом деле вне.

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

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

Конечно, это все чистое предположение. Опять же, без полного примера кода, действительно невозможно точно сказать, какой механизм синхронизации будет лучше всего в вашем сценарии. Но, учитывая предполагаемую стоимость сбора данных, похоже, что управление собственной синхронизацией может быть лучше, чем использование ConcurrentDictionary<TKey, TValue>, учитывая, что последнее не обеспечивает прямой способ избежать сбора данных более одного раза.

Следует ли использовать ReaderWriterLockSlim вместо простого подхода lock, является еще более спекулятивным.Это возможно бывший лучше, но отсутствует больше информации, это так же вероятно, что это не так.

+0

уверен, что третья рука другая другая рука, или это то, что первая рука снова? :-) – Jodrell

+0

@Peter Duniho, Спасибо, что ваш ответ был полезен до сих пор. Я скоро отредактирую код, чтобы уточнить его больше. Однако, насколько я понимаю, может быть только один поток, содержащий UpgradeableReadLock (хотя он позволяет другим потокам заканчиваться, если они держат Readlock), и поэтому я думал, что точка, которую вы сделали под третьей рукой, невозможна. [ссылка MSDN] (https://msdn.microsoft.com/en-us/library/system.threading.readerwriterlockslim%28v=vs.110%29.aspx) – Igavshne

+0

@Mikrur: извините, да, вы правы. Прежде чем написать это, я должен был дважды проверить свою память. Я отредактировал вопрос, чтобы удалить этот ошибочный комментарий, и уточнить мои общие (и, по общему признанию, расплывчатые) мысли и выводы, основанные на (по общему признанию, неопределенной) постановке проблемы. –

Смежные вопросы