Блокировка в словаре TryGetValue () - проблемы с производительностью - PullRequest
6 голосов
/ 04 мая 2011

Я профилировал свое приложение и провел несколько тестов производительности, которые заставили меня поверить, что следующая схема if-lock-if:

private float GetValue(int id)
{
    float value;
    if (!dictionary.TryGetValue(id, out value))
    {
      lock (lockObj)
      {
        if (!dictionary.TryGetValue(id, out value))
        {
          value = ComputeValue(id);
          dictionary.Add(id, value);
        }
      }
    }
}

, кажется, работает быстрее, чем "lock-if" или использованиеReaderWriterLockSlim.Но очень редко я получаю следующее исключение:

1) Exception Information
*********************************************
Exception Type: System.NullReferenceException
Message: Object reference not set to an instance of an object.
Data: System.Collections.ListDictionaryInternal
TargetSite: Int32 FindEntry(TKey)
HelpLink: NULL
Source: mscorlib

StackTrace Information
*********************************************
  at System.Collections.Generic.Dictionary`2.FindEntry(TKey key)
  at System.Collections.Generic.Dictionary`2.TryGetValue(TKey key, TValue& value)
  at MyNamespace.GetValue()
  .....
  .....

Что я здесь не так делаю?

Редактировать: чтобы уточнить, этот метод вызывается в среднем более 50 миллионов раз, иконфликт обычно меньше 5000.

Спасибо

Ответы [ 5 ]

15 голосов
/ 04 мая 2011

То, что вы пытаетесь сделать здесь, просто не поддерживается сценарием.TryGetValue происходит вне блокировки, что означает, что один поток может писать в словарь, в то время как другие одновременно вызывают TryGetValue.Единственный сценарий потоков, поддерживаемый Dictionary<TKey, TValue>, - это чтение из нескольких потоков.Как только вы начинаете читать и писать из нескольких потоков, все ставки отключены.

Чтобы сделать это безопасным, вы должны выполнить одно из следующих действий:

  • Использовать одну блокировку для всех операций чтения или записи для Dictionary
  • Использованиетип как ConcurrentDictionary<TKey, TValue>, который предназначен для многопоточных сценариев.
3 голосов
/ 04 мая 2011

Либо использование этой коллекции вашим кодом является поточно-ориентированным, в этом случае вам не нужна блокировка, либо оно не поточно-ориентировано, и в этом случае вам ВСЕГДА нужна блокировка.

Попробуйте использовать ConcurrentDictionary вместо этого, что является потокобезопасным.

2 голосов
/ 04 мая 2011

Dictionary не является потокобезопасным. Если что-то добавляет в словарь, когда вы делаете TryGetValue, все может пойти плохо. Ваш первый звонок на TryGetValue не защищен блокировкой. Таким образом, если поток A выполняет Add и поток B вводит этот первый TryGetValue, он может выдать исключение.

Рассмотрите возможность использования System.Collections.Concurrent.ConcurrentDictionary. Или обязательно заблокируйте словарь на при каждом доступе. Возможно, используя ReaderWriterLockSlim.

0 голосов
/ 25 марта 2019

Вы можете обернуть без блокировки TryGetValue с помощью try catch и retry / fallback для блокировки, если вы получите исключение. Таким образом, в общем случае, когда нет записи или вам повезло, и вы не получили исключение во время записи, вы можете доверять значению, возвращенному TryGetValue ..

0 голосов
/ 04 мая 2011

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

Есть ли причина для выполнения одного и того же оператора if дважды?

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