Surveiller vs verrouiller

89

Quand est-il approprié d'utiliser la Monitorclasse ou le lockmot - clé pour la sécurité des threads en C #?

EDIT: Il semble, d'après les réponses jusqu'à présent, que ce locksoit un raccourci pour une série d'appels à la Monitorclasse. À quoi sert exactement le raccourci d'appel de verrouillage? Ou plus explicitement,

class LockVsMonitor
{
    private readonly object LockObject = new object();
    public void DoThreadSafeSomethingWithLock(Action action)
    {
        lock (LockObject)
        {
            action.Invoke();
        }
    }
    public void DoThreadSafeSomethingWithMonitor(Action action)
    {
        // What goes here ?
    }
}

Mise à jour

Merci à tous pour votre aide: j'ai posté une autre question en réponse à certaines des informations que vous avez toutes fournies. Puisque vous semblez bien connaître ce domaine, j'ai publié le lien: Quel est le problème avec cette solution pour verrouiller et gérer les exceptions verrouillées?

smartcaveman
la source

Réponses:

89

Eric Lippert en parle dans son blog: Les verrous et les exceptions ne se mélangent pas

Le code équivalent diffère entre C # 4.0 et les versions antérieures.


En C # 4.0, c'est:

bool lockWasTaken = false;
var temp = obj;
try
{
    Monitor.Enter(temp, ref lockWasTaken);
    { body }
}
finally
{
    if (lockWasTaken) Monitor.Exit(temp);
}

Il repose sur la Monitor.Enterdéfinition atomique de l'indicateur lorsque le verrou est pris.


Et plus tôt c'était:

var temp = obj;
Monitor.Enter(temp);
try
{
   body
}
finally
{
    Monitor.Exit(temp);
}

Cela dépend du fait qu'aucune exception n'est lancée entre Monitor.Enteret le try. Je pense que dans le code de débogage, cette condition a été violée car le compilateur a inséré un NOP entre eux et a donc rendu possible l'interruption de thread entre ceux-ci.

CodesInChaos
la source
Comme je l'ai dit, le premier exemple est C # 4 et l'autre est ce que les versions antérieures utilisent.
CodesInChaos
En remarque, C # via CLR mentionne une mise en garde concernant le mot-clé lock: vous pouvez souvent vouloir faire quelque chose pour restaurer l'état corrompu (le cas échéant) avant de libérer le verrou. Puisque le mot-clé lock ne nous permet pas de mettre les choses dans le bloc catch, nous devrions envisager d'écrire la version longue try-catch-finally pour les routines non triviales.
kizzx2
5
La restauration de l'état partagé par l'OMI est orthogonale au verrouillage / multi-threading. Cela devrait donc être fait avec un try-catch / finally à l'intérieur du lockbloc.
CodesInChaos le
2
@ kizzx2: Un tel modèle serait particulièrement intéressant avec les verrous lecteur-écrivain. Si une exception se produit dans le code qui détient un verrou de lecteur, il n'y a aucune raison de s'attendre à ce que la ressource protégée puisse être endommagée, et donc aucune raison de l'invalider. Si une exception se produit dans un verrou d'écriture et que le code de gestion des exceptions n'indique pas expressément que l'état de l'objet protégé a été réparé, cela suggérerait que l'objet peut être endommagé et doit être invalidé. À mon humble avis, les exceptions inattendues ne devraient pas faire planter un programme, mais devraient invalider tout ce qui pourrait être corrompu.
supercat
2
@ArsenZahray Vous n'avez pas besoin Pulsed'un simple verrouillage. C'est important dans certains scénarios de multi-threading avancés. Je ne l'ai jamais utilisé Pulsedirectement.
CodesInChaos
43

lockest juste un raccourci pour Monitor.Enteravec try+ finallyet Monitor.Exit. Utilisez l'instruction de verrouillage chaque fois que cela suffit - si vous avez besoin de quelque chose comme TryEnter, vous devrez utiliser Monitor.

Lukáš Novotný
la source
23

Une instruction de verrouillage équivaut à:

Monitor.Enter(object);
try
{
   // Your code here...
}
finally
{
   Monitor.Exit(object);
}

Cependant, gardez à l'esprit que Monitor peut également Wait () et Pulse () , qui sont souvent utiles dans des situations de multithreading complexes.

Mise à jour

Cependant, en C # 4, il est implémenté différemment:

bool lockWasTaken = false;
var temp = obj;
try 
{
     Monitor.Enter(temp, ref lockWasTaken); 
     //your code
}
finally 
{ 
     if (lockWasTaken) 
             Monitor.Exit(temp); 
} 

Merci à CodeInChaos pour les commentaires et les liens

Shekhar_Pro
la source
En C # 4, l'instruction de verrouillage est implémentée différemment. blogs.msdn.com/b/ericlippert/archive/2009/03/06/…
CodesInChaos
14

Monitorest plus flexible. Mon cas d'utilisation préféré de l'utilisation du moniteur est lorsque vous ne voulez pas attendre votre tour et simplement sauter :

//already executing? forget it, lets move on
if(Monitor.TryEnter(_lockObject))
{
    //do stuff;
    Monitor.Exit(_lockObject);
}
Alex
la source
6

Comme d'autres l'ont dit, lockest «équivalent» à

Monitor.Enter(object);
try
{
   // Your code here...
}
finally
{
   Monitor.Exit(object);
}

Mais juste par curiosité, lockconservera la première référence que vous lui passerez et ne la lancera pas si vous la changez. Je sais qu'il n'est pas recommandé de changer l'objet verrouillé et que vous ne voulez pas le faire.

Mais encore une fois, pour la science, cela fonctionne bien:

var lockObject = "";
var tasks = new List<Task>();
for (var i = 0; i < 10; i++)
    tasks.Add(Task.Run(() =>
    {
        Thread.Sleep(250);
        lock (lockObject)
        {
            lockObject += "x";
        }
    }));
Task.WaitAll(tasks.ToArray());

... Et cela ne fait pas:

var lockObject = "";
var tasks = new List<Task>();
for (var i = 0; i < 10; i++)
    tasks.Add(Task.Run(() =>
    {
        Thread.Sleep(250);
        Monitor.Enter(lockObject);
        try
        {
            lockObject += "x";
        }
        finally
        {
            Monitor.Exit(lockObject);
        }
    }));
Task.WaitAll(tasks.ToArray());

Erreur:

Une exception de type «System.Threading.SynchronizationLockException» s'est produite dans 70783sTUDIES.exe mais n'a pas été gérée dans le code utilisateur

Informations supplémentaires: La méthode de synchronisation d'objets a été appelée à partir d'un bloc de code non synchronisé.

C'est parce que Monitor.Exit(lockObject);va agir sur lockObjectce qui a changé parce que stringssont immuables, alors vous l'appelez à partir d'un bloc de code non synchronisé .. mais de toute façon. C'est juste un fait amusant.

André Pena
la source
"C'est parce que Monitor.Exit (lockObject); agira sur lockObject". Alors le verrouillage ne fait rien avec l'objet? Comment fonctionne le verrouillage?
Yugo Amaryl
@YugoAmaryl, je suppose que c'est parce que l'instruction de verrouillage garde à l'esprit la première référence passée, puis l'utilise au lieu d'utiliser la référence modifiée, comme:object temp = lockObject; Monitor.Enter(temp); <...locked code...> Monitor.Exit(temp);
Zhuravlev A.
3

Les deux sont la même chose. lock est un mot-clé c sharp et utilise la classe Monitor.

http://msdn.microsoft.com/en-us/library/ms173179(v=vs.80).aspx

RobertoBr
la source
3
Regardez msdn.microsoft.com/en-us/library/ms173179(v=vs.80).aspx "En fait, le mot clé lock est implémenté avec la classe Monitor. Par exemple"
RobertoBr
1
l'implémentation sous-jacente de lock utilise Monitor mais ce n'est pas la même chose, considérez les méthodes fournies par monitor qui n'existent pas pour le verrouillage, et la façon dont vous pouvez verrouiller et déverrouiller dans des blocs de code séparés.
eran otzap
3

Le verrouillage et le comportement de base du moniteur (entrée + sortie) sont plus ou moins les mêmes, mais le moniteur a plus d'options qui vous permettent plus de possibilités de synchronisation.

Le verrou est un raccourci, et c'est l'option pour l'utilisation de base.

Si vous avez besoin de plus de contrôle, le moniteur est la meilleure option. Vous pouvez utiliser Wait, TryEnter et Pulse, pour des usages avancés (comme les barrières, les sémaphores, etc.).

Borja
la source
1

Le mot clé Lock Lock garantit qu'un thread exécute un morceau de code à la fois.

lock (lockObject)

        {
        //   Body
        }

Le mot clé lock marque un bloc d'instructions comme une section critique en obtenant le verrou d'exclusion mutuelle pour un objet donné, en exécutant une instruction puis en libérant le verrou

Si un autre thread essaie d'entrer un code verrouillé, il attendra, se bloque, jusqu'à ce que l'objet soit libéré.

Monitor Le moniteur est une classe statique et appartient à l'espace de noms System.Threading.

Il fournit un verrouillage exclusif sur l'objet afin qu'un seul thread puisse entrer dans la section critique à un moment donné.

Différence entre Monitor et Lock en C #

Le verrou est le raccourci pour Monitor.Entrez avec try et enfin. Les poignées de verrouillage essaient et enfin bloquent en interne Lock = Monitor + essayez enfin.

Si vous voulez plus de contrôle pour mettre en œuvre des solutions de multithreading avancées en utilisant TryEnter() Wait(), Pulse()et les PulseAll()méthodes, la classe Monitor est votre option.

C # Monitor.wait(): un thread attend que d'autres threads soient notifiés.

Monitor.pulse(): Un thread notifie à un autre thread.

Monitor.pulseAll(): Un thread notifie tous les autres threads d'un processus

Aatrey
la source
0

En plus de toutes les explications ci-dessus, lock est une instruction C # tandis que Monitor est une classe de .NET située dans l'espace de noms System.Threading.

PureSilence
la source