5

我有一个应用程序内服务,它允许我向它提供来自各种来源的消息,这些消息将被放入一个简单的列表中。该服务在自己的线程中运行,将定期将列表中的所有消息处理成各种文件;每个源一个文件,然后对其大小进行管理。

我的问题是关于检查消息和对访问列表的代码执行锁定的正确方法。只有两个地方可以访问该列表;一种是将消息添加到列表的位置,另一种是将消息从列表转储到处理列表中的位置。

将消息添加到列表:

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...

我的问题是:我在处理列表时是否应该仔细检查,见下文?为什么?或者为什么不呢?在锁之外访问列表的计数属性是否有任何危险?哪种方法更好或更有效?为什么?或者为什么不呢?

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...

我知道在这种特殊情况下,考虑到在处理功能之外,列表只能增长这一事实,我是否进行仔细检查可能并不重要。但这是我的工作示例,我正在尝试了解线程的更详细信息。

提前感谢您的任何见解......</p>


经过一些进一步的研究,谢谢'浣熊',以及一些实验性编程,我有一些进一步的想法。

关于ReaderWriterLockSlim,我有以下示例,它似乎工作正常。它允许我读取列表中的消息数量,而不会干扰可能试图读取列表中消息数量或消息本身的其他代码。当我想要处理列表时,我可以将我的锁升级为写入模式,将消息转储到处理列表中并在任何读/写锁之外处理它们,因此不会阻塞任何其他可能想要添加或读取的线程, 更多消息。

请注意,这个例子使用了一个更简单的消息结构,一个字符串,而不是前面的例子,它使用了一个类型和一些其他元数据。

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

Simplicity itself…</p>

4

1 回答 1

3

Theory:

Once a thread acquires the _SyncLockObject lock all other threads reentering that method will have to wait for the lock to be released.

So the check before and after the lock is irrelevant. In other words, it will have no effect. It is also not safe, because you're not using a concurrent list.

If one thread happens to check the Count in the first test while another is clearing or adding to the collection, then you'll get an exception with Collection was modified; enumeration operation may not execute.. Also, the second check can only be executed by one thread at a time (since it's synced).

This applies for your Add method as well. While the lock is owned by one thread (meaning the execution flow has reached that line), no other threads will be able to process or add to the list.

如果您只是从应用程序中其他位置的列表中读取,您也应该小心锁定。对于更复杂的读/写场景(例如自定义并发集合),我建议使用ReaderWriterLockSlim.

实践:

使用BlockingCollection,因为它是线程安全的(即它在内部处理并发)。

于 2012-07-03T18:21:39.063 回答