5

簡単なリストに入れられるさまざまなソースからのメッセージをフィードできるアプリケーション内サービスがあります。独自のスレッドで実行されるサービスは、定期的に、リスト内のすべてのメッセージをさまざまなファイルに処理します。ソースごとに 1 つのファイルが作成され、サイズが管理されます。

私の質問は、メッセージをチェックし、リストにアクセスするコードをロックする適切な方法についてです。リストにアクセスできる場所は 2 つだけです。1 つはメッセージがリストに追加される場所で、もう 1 つはメッセージがリストから処理リストにダンプされる場所です。

リストへのメッセージの追加:

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

この特定のケースでは、処理関数の外ではリストが大きくなるだけであるという事実を考慮して、二重チェックを行っても問題ない場合があることを理解しています。しかし、これは私の実際の例であり、スレッド化の詳細について学ぼうとしています。

洞察をお寄せいただきありがとうございます…</p>


いくつかのさらなる調査の後、「クーン」に感謝し、いくつかの実験的なプログラミングを行った後、さらにいくつかの考えがあります。

ReaderWriterLockSlimに関しては、うまくいくように見える次の例があります。リスト内のメッセージ数やメッセージ自体を読み取ろうとしている可能性のある他のコードに干渉することなく、リスト内のメッセージ数を読み取ることができます。また、リストを処理したい場合は、ロックを書き込みモードにアップグレードし、メッセージを処理リストにダンプして、読み取り/書き込みロックの外側で処理できるため、追加または読み取りが必要な他のスレッドをブロックしません。 、その他のメッセージ。

この例では、他のメタデータとともに Type を使用した前の例とは対照的に、メッセージに単純な構造体である 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

シンプルさそのもの…</p>

4

1 に答える 1

3

仮説:

スレッドがロックを取得すると、_SyncLockObjectそのメソッドに再入する他のすべてのスレッドは、ロックが解放されるまで待機する必要があります。

したがって、ロックの前後のチェックは無関係です。つまり、効果がなくなります。また、同時リストを使用していないため、安全ではありません。

Countあるスレッドが最初のテストでたまたま をチェックし、別のスレッドがコレクションをクリアまたは追加している場合、 で例外が発生しCollection was modified; enumeration operation may not execute.ます。また、2 番目のチェックは、一度に 1 つのスレッドによってのみ実行できます (同期されているため)。

これは Add メソッドにも当てはまります。ロックが 1 つのスレッドによって所有されている間 (実行フローがその行に到達したことを意味します)、他のスレッドは処理したりリストに追加したりできません。

アプリケーションの他の場所でリストから読み取るだけの場合も、ロックするように注意する必要があります。より複雑な読み取り/書き込みシナリオ (カスタムの同時実行コレクションなど) の場合は、 を使用することをお勧めしReaderWriterLockSlimます。

練習:

BlockingCollectionはスレッド セーフであるため (つまり、同時実行性を内部で処理します) 、BlockingCollectionを使用します。

于 2012-07-03T18:21:39.063 に答える