2013-08-21 3 views
1

Вчера я обнаружил, мы имели многопоточности вопрос с помощью простого объекта кэширования мы используем:Какой фиксирующий примитив использовать?

If Dictionary.Contains(lsKey.ToLower) Then 'if rate cached, then return value 
     lvResult = Dictionary.Item(lsKey.ToLower) 

    Else 'else retrieve from database, store, and return value 
     lvResult = GetRateFromDB(voADO, 
           veRateType, 
           vdEffDate) 
     Dictionary.Add(lsKey.ToLower, lvResult) 

    End If 

Мы обнаружили эту проблему на нашем сайте asp.net. Сообщение об ошибке прочитало что-то вроде «вы пытаетесь добавить значение к хеш-таблице, которая уже существует. Как вы можете сказать из вышеприведенного кода, потенциал, безусловно, завершается, чтобы это произошло. Я был немного знаком с waithandles и думал, что они разрешат проблему . Таким образом, я объявил мой WaitHandle на уровне класса:

private Shared _waitHandle as new AutoResetEvent(True) 

Затем в специальном разделе кода с проблемой:

_waitHandle.Wait() 
If Dictionary.Contains(lsKey.ToLower) Then 'if rate cached, then return value 
    lvResult = Dictionary.Item(lsKey.ToLower) 

Else 'else retrieve from database, store, and return value 
    lvResult = GetRateFromDB(voADO, 
          veRateType, 
          vdEffDate) 
    Dictionary.Add(lsKey.ToLower, lvResult) 
End If 
_waitHandle.Set() 

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

я в конечном итоге, используя следующие вместо которой работает отлично:

SyncLock loLock 
    If Dictionary.Contains(lsKey.ToLower) Then 'if rate cached, then return value 
     lvResult = Dictionary.Item(lsKey.ToLower) 

    Else 'else retrieve from database, store, and return value 
     lvResult = GetRateFromDB(voADO, 
           veRateType, 
           vdEffDate) 
     Dictionary.Add(lsKey.ToLower, lvResult) 

    End If 
End SyncLock 

Поэтому у меня есть два вопроса:

  1. Почему не работу WaitHandle решение?
  2. Является ли SynLock правильным/оптимизированным типом блокировки для использования в этом случае?
+0

Переход на ['ConcurrentDictionary'] (http://msdn.microsoft.com/en-us/library/dd287191.aspx) будет гораздо более надежным решением, если вы используете .NET 4 или более позднюю версию , –

+0

Если вы переключитесь на 'ConcurrentDictionary', вам нужно будет использовать метод AddOrUpdate(). Если вы просто сохранили вышеуказанный шаблон ('if .Contains(), else .Add()'), у вас все еще есть условие гонки, так как поток может добавить тот же ключ после вызова .Contains(). –

+0

Хотя 'AutoResetEvent', вероятно, не является подходящим методом блокировки, объяснения, данные двумя ответами до сих пор, похоже, не правильно объясняют, почему метод' AutoResetEvent', который вы пробовали, не работал. Одна из возможностей заключается в том, что исключение было выбрано во время 'GetRateFromDB', предотвращая вызов' _waitHandle.Set() ', и поэтому все потоки в конечном итоге блокируются на' Wait() '. Помещение 'Set()' в блок 'finally', скорее всего, решит это (хотя опять же, есть более эффективные методы синхронизации, чем' AutoResetEvent'). – Iridium

ответ

1

1 waithandles блок до тех пор, пока не будет сигнализирован. Вам нужно что-то где-то сообщить, что waithandle для него не блокировать первый поток, чтобы получить доступ. Я считаю, что это сработает, если вы указали дескриптор в конструкторе, где вы создали waithandle. Подумайте, как есть слот для сигнала внутри waithandle любой поток, который вызывает wait, будет ждать, пока он не сможет потреблять сигнал, прежде чем покинуть вызов ожидания.

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

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

1

Окна Условия:

  • An "Event" позволяет один поток (или процесс) сигнализировать другой поток (или процесс).
  • A "Семафор" аналогичен событию, но может использоваться для сигнализации (пробуждения) определенного количества потоков.
  • A «Критический раздел» используется для предотвращения одновременного доступа к блоку кода.
  • A «Slim Lock» похож на критический раздел, но обычно не позволяет нитью, которая владеет блокировкой, вводить ее несколько раз (что разрешено с использованием критического раздела).
  • A «« Читатель Writer »блокировка дает вам возможность блокировать объект в эксклюзивном режиме (аналогично критическому разделу) или в режиме общего доступа, который позволяет нескольким потокам выполнять один и тот же блок.
  • Для полноты есть также «Mutex», который очень похож на критический раздел, но может использоваться совместно между процессами.

Таким образом, используя AutoResetEvent выше, определенно не то, что вы хотите. Когда поток вызывает один из методов Wait, он блокируется до тех пор, пока не получит сигнал от другого потока. Никто никогда не сигнализирует вам, поэтому вы ждете вечности.

Оператор SyncLock использует критический участок под крышками и не позволяет нитью входить в один и тот же фрагмент кода одновременно. Это даст вам необходимую защиту. Но поскольку вам нужно защитить все обращения к объекту Dictionary, чтобы избежать повреждения, вам нужно использовать блокировку везде, где вы используете объект Dictionary.

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