5 votes

Dois-je vérifier deux fois avant et après le verrouillage d'une liste ?

Je dispose d'un service intégré à l'application qui me permet de l'alimenter en messages provenant de diverses sources, qui seront placés dans une simple liste. Le service, qui s'exécute dans son propre thread, traitera périodiquement tous les messages de la liste dans différents fichiers, un fichier pour chaque source, qui seront ensuite gérés en fonction de leur taille.

Ma question porte sur la manière appropriée de vérifier les messages et d'effectuer un verrouillage autour du code qui accède à la liste. Il n'y a que deux endroits qui accèdent à la liste ; l'un est celui où un message est ajouté à la liste et l'autre est celui où les messages sont transférés de la liste à une liste de traitement.

Ajout d'un message à la liste :

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

Traitement de la liste :

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

Mes questions sont les suivantes : dois-je faire un double contrôle lorsque je traite la liste, voir ci-dessous ? Et pourquoi ? Ou pourquoi pas ? Et y a-t-il un danger à accéder à la propriété count de la liste en dehors du verrou ? L'une ou l'autre des méthodes est-elle meilleure ou plus efficace ? Et pourquoi ? Ou pourquoi pas ?

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

Je comprends que dans ce cas particulier, il n'est peut-être pas important que je fasse une double vérification étant donné que, en dehors de la fonction de traitement, la liste ne peut que s'allonger. Mais c'est mon exemple de travail et j'essaie d'apprendre les détails les plus fins du threading.

Merci d'avance pour tout commentaire


Après quelques recherches supplémentaires, merci "le raton laveur", et une programmation expérimentale, j'ai quelques idées supplémentaires.

Concernant le ReaderWriterLockSlim J'ai l'exemple suivant qui semble fonctionner correctement. Il me permet de lire le nombre de messages dans la liste sans interférer avec d'autres codes qui pourraient essayer de lire le nombre de messages dans la liste, ou les messages eux-mêmes. Et lorsque je souhaite traiter la liste, je peux faire passer mon verrou en mode écriture, déverser les messages dans une liste de traitement et les traiter en dehors de tout verrou de lecture/écriture, sans bloquer les autres threads qui pourraient vouloir ajouter ou lire d'autres messages.

Veuillez noter que cet exemple utilise une construction plus simple pour le message, une chaîne, par opposition à l'exemple précédent qui utilisait un type avec d'autres métadonnées.

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

Le seul problème que je vois avec cette technique est que le développeur doit être diligent pour acquérir et libérer les verrous. Sinon, des blocages se produiront.

Quant à savoir si cette méthode est plus efficace que l'utilisation d'un fichier de type SyncLock je ne saurais vraiment pas dire. Pour cet exemple particulier et son utilisation, je pense que l'un ou l'autre suffirait. Je ne ferais pas de double vérification pour les mêmes raisons que celles invoquées par "the coon" concernant la lecture du compte pendant que quelqu'un d'autre le modifie. Dans cet exemple, le SyncLock fournirait la même fonctionnalité. Toutefois, dans un système un peu plus complexe, dans lequel plusieurs sources pourraient lire et écrire dans la liste, l'option ReaderWriterLockSlim serait idéal.


Concernant le BlocageCollection l'exemple suivant fonctionne comme celui qui précède.

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

La simplicité même

3voto

Marcel N. Points 7908

La théorie :

Une fois qu'un thread acquiert le _SyncLockObject tous les autres threads entrant à nouveau dans cette méthode devront attendre que le verrou soit libéré.

Le contrôle avant et après le verrouillage n'est donc pas pertinent. En d'autres termes, elle n'aura aucun effet. Il est également pas sûr parce que vous n'utilisez pas une liste concurrente.

Si un thread vérifie le Count dans le premier test alors qu'un autre est en train d'effacer ou d'ajouter à la collection, alors vous obtiendrez une exception avec Collection was modified; enumeration operation may not execute. . De plus, la deuxième vérification ne peut être exécutée que par un seul thread à la fois (puisqu'elle est synchronisée).

Cela vaut également pour votre méthode d'ajout. Tant que le verrou est détenu par un thread (ce qui signifie que le flux d'exécution a atteint cette ligne), aucun autre thread ne pourra traiter ou ajouter à la liste.

Vous devez faire attention à verrouiller également si vous ne faites que lire la liste à d'autres endroits de votre application. Pour des scénarios de lecture/écriture plus complexes (comme une collection concurrente personnalisée), je recommande l'utilisation de ReaderWriterLockSlim .

La pratique :

Utilisez un BlocageCollection puisqu'il est thread safe (c'est-à-dire qu'il gère la concurrence en interne).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X