Должен ли я перепроверить до и после блокировки списка?
У меня есть сервис в приложении, который позволяет мне кормить его сообщениями из разных источников, которые будут помещены в простой список. Служба, работающая в своем собственном потоке, будет периодически обрабатывать все сообщения в списке в различные файлы; один файл для каждого источника, который затем управляется по размеру.
Мой вопрос касается правильного способа проверки сообщений и выполнения блокировки кода, который обращается к списку. Есть только два места, которые получают доступ к списку; один - это место, где сообщение добавляется в список, а второй - место, где сообщения сбрасываются из списка в список обработки.
Добавление сообщения в список:
Public Sub WriteMessage(ByVal messageProvider As IEventLogMessageProvider, ByVal logLevel As EventLogLevel, ByVal message As String)
SyncLock _SyncLockObject
_LogMessages.Add(New EventLogMessage(messageProvider, logLevel, Now, message))
End SyncLock
End Sub
Обработка списка:
Dim localList As New List(Of EventLogMessage)
SyncLock _SyncLockObject
If (_LogMessages.Count > 0) Then
localList.AddRange(_LogMessages)
_LogMessages.Clear()
End If
End SyncLock
' process list into files...
Мои вопросы: должен ли я выполнить двойную проверку при обработке списка, см. Ниже? И почему? Или почему нет? И есть ли опасности в доступе к свойству count списка за пределами блокировки? Один из методов лучше или эффективнее? И почему? Или почему нет?
Dim localList As New List(Of EventLogMessage)
If (_LogMessages.Count > 0) Then
SyncLock _SyncLockObject
If (_LogMessages.Count > 0) Then
localList.AddRange(_LogMessages)
_LogMessages.Clear()
End If
End SyncLock
End If
' process list into files...
Я понимаю, что в данном конкретном случае, возможно, не имеет значения, если я сделаю двойную проверку, учитывая тот факт, что вне функции обработки список может только расти. Но это мой рабочий пример, и я пытаюсь узнать о мельчайших деталях потоков.
Заранее спасибо за любые идеи...
После некоторых дальнейших исследований, спасибо "кун", и некоторых экспериментальных программ, у меня есть некоторые дальнейшие мысли.
Что касается ReaderWriterLockSlim, у меня есть следующий пример, который, кажется, работает нормально. Это позволяет мне читать количество сообщений в списке, не мешая другому коду, который может пытаться прочитать количество сообщений в списке или сами сообщения. И когда я захочу обработать список, я могу обновить свою блокировку до режима записи, выгрузить сообщения в список обработки и обработать их вне каких-либо блокировок чтения / записи, таким образом не блокируя другие потоки, которые могут захотеть добавить или прочитать больше сообщений.
Обратите внимание, что в этом примере используется более простая конструкция для сообщения, String, в отличие от предыдущего примера, в котором использовался тип вместе с некоторыми другими метаданными.
Private _ReadWriteLock As New Threading.ReaderWriterLockSlim()
Private Sub Process()
' create local processing list
Dim processList As New List(Of String)
Try
' enter read lock mode
_ReadWriteLock.EnterUpgradeableReadLock()
' if there are any messages in the 'global' list
' then dump them into the local processing list
If (_Messages.Count > 0) Then
Try
' upgrade to a write lock to prevent others from writing to
' the 'global' list while this reads and clears the 'global' list
_ReadWriteLock.EnterWriteLock()
processList.AddRange(_Messages)
_Messages.Clear()
Finally
' alway release the write lock
_ReadWriteLock.ExitWriteLock()
End Try
End If
Finally
' always release the read lock
_ReadWriteLock.ExitUpgradeableReadLock()
End Try
' if any messages were dumped into the local processing list, process them
If (processList.Count > 0) Then
ProcessMessages(processList)
End If
End Sub
Private Sub AddMessage(ByVal message As String)
Try
' enter write lock mode
_ReadWriteLock.EnterWriteLock()
_Messages.Add(message)
Finally
' always release the write lock
_ReadWriteLock.ExitWriteLock()
End Try
End Sub
Единственная проблема, которую я вижу с этой техникой, заключается в том, что разработчик должен быть усердным в приобретении и снятии блокировок. В противном случае возникнут взаимоблокировки.
Относительно того, является ли это более эффективным, чем использование SyncLock, я действительно не могу сказать. Для этого конкретного примера и его использования, я полагаю, будет достаточно любого из них. Я бы не стал делать двойную проверку по тем самым причинам, которые "кун" дал о чтении счета, в то время как кто-то другой меняет его. Учитывая этот пример, SyncLock обеспечит ту же функциональность. Однако в немного более сложной системе, в которой несколько источников могут читать и записывать в список, ReaderWriterLockSlim будет идеальным.
Что касается списка BlockingCollection, следующий пример работает аналогично приведенному выше.
Private _Messages As New System.Collections.Concurrent.BlockingCollection(Of String)
Private Sub Process()
' process each message in the list
For Each item In _Messages
ProcessMessage(_Messages.Take())
Next
End Sub
Private Sub AddMessage(ByVal message As String)
' add a message to the 'global' list
_Messages.Add(message)
End Sub
Сама простота…
1 ответ
Теория:
Как только поток получает _SyncLockObject
заблокируйте все остальные потоки, повторно входящие в этот метод, придется ждать снятия блокировки.
Поэтому проверка до и после блокировки не имеет значения. Другими словами, это не будет иметь никакого эффекта. Это также не безопасно, потому что вы не используете параллельный список.
Если один поток случится проверить Count
в первом тесте, пока другой очищает или добавляет в коллекцию, вы получите исключение с Collection was modified; enumeration operation may not execute.
, Кроме того, вторая проверка может выполняться только одним потоком за раз (поскольку она синхронизирована).
Это относится и к вашему методу Add. Хотя блокировка принадлежит одному потоку (то есть поток выполнения достиг этой строки), никакие другие потоки не смогут обработать или добавить в список.
Вы должны быть осторожны, чтобы также заблокировать, если вы просто читаете из списка в некоторых других местах в вашем приложении. Для более сложных сценариев чтения / записи (таких как настраиваемая одновременная коллекция) я рекомендую использовать ReaderWriterLockSlim
,
Практика:
Используйте BlockingCollection, так как он является потокобезопасным (то есть он обрабатывает параллелизм внутри).