Recherche…


Syntaxe

  • verrouiller (obj) {}

Remarques

À l'aide de l'instruction de lock , vous pouvez contrôler l'accès des différents threads au code dans le bloc de code. Il est couramment utilisé pour empêcher les conditions de concurrence, par exemple, la lecture et la suppression d'éléments multiples dans une collection. Comme le verrouillage oblige les threads à attendre que d'autres threads quittent un bloc de code, cela peut entraîner des retards qui pourraient être résolus avec d'autres méthodes de synchronisation.

MSDN

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

Le mot-clé lock garantit qu'un thread n'entre pas dans une section critique du code alors qu'un autre thread se trouve dans la section critique. Si un autre thread tente d'entrer un code verrouillé, il attendra, bloquera, jusqu'à ce que l'objet soit libéré.

La meilleure pratique consiste à définir un objet privé à verrouiller ou une variable d'objet statique privée pour protéger les données communes à toutes les instances.


Dans C # 5.0 et versions ultérieures, la déclaration de lock est équivalente à:

bool lockTaken = false;
try 
{
    System.Threading.Monitor.Enter(refObject, ref lockTaken);
    // code 
}
finally 
{
    if (lockTaken)
        System.Threading.Monitor.Exit(refObject);
}

Pour C # 4.0 et les versions antérieures, l'instruction de lock est équivalente à:

System.Threading.Monitor.Enter(refObject);
try 
{
    // code
}
finally 
{
     System.Threading.Monitor.Exit(refObject);
}

Usage simple

L'utilisation commune du lock est une section critique.

Dans l'exemple suivant, ReserveRoom doit être appelé à partir de différents threads. La synchronisation avec le lock est le moyen le plus simple de prévenir les conditions de course. Le corps de la méthode est entouré d'un lock qui garantit que deux threads ou plus ne peuvent pas l'exécuter simultanément.

public class Hotel
{
    private readonly object _roomLock = new object();

    public void ReserveRoom(int roomNumber)
    {
        // lock keyword ensures that only one thread executes critical section at once
        // in this case, reserves a hotel room of given number
        // preventing double bookings
        lock (_roomLock)
        {
            // reserve room logic goes here
        }
    }
}

Si un thread atteint lock bloc verrouillé pendant qu'un autre thread s'exécute dans celui-ci, le premier attendra un autre pour quitter le bloc.

La meilleure pratique consiste à définir un objet privé à verrouiller ou une variable d'objet statique privée pour protéger les données communes à toutes les instances.

Lancer une exception dans une déclaration de verrouillage

Le code suivant libère le verrou. Il n'y aura pas de problème. L'énoncé de verrouillage des coulisses fonctionne comme try finally

lock(locker)
{
    throw new Exception();
}

Plus peut être vu dans la spécification C # 5.0 :

Une déclaration de lock du formulaire

lock (x) ...

x est une expression d'un type de référence , est précisément équivalent à

bool __lockWasTaken = false;
try {
    System.Threading.Monitor.Enter(x, ref __lockWasTaken);
    ...
}
finally {
    if (__lockWasTaken) System.Threading.Monitor.Exit(x);
}

sauf que x n'est évalué qu'une seule fois.

Retourner dans une déclaration de verrouillage

Le code suivant libère le verrou.

lock(locker)
{
    return 5;
}

Pour une explication détaillée, cette réponse SO est recommandée.

Utilisation d'instances d'objet pour le verrouillage

Lorsque vous utilisez la déclaration de lock intégrée à C #, une instance de quelque type est nécessaire, mais son état importe peu. Une instance d' object est parfaite pour cela:

public class ThreadSafe {
  private static readonly object locker = new object();


  public void SomeThreadSafeMethod() {
    lock (locker) {
      // Only one thread can be here at a time.
    }
  }
}

NB les instances de Type ne doivent pas être utilisées pour cela (dans le code au-dessus de typeof(ThreadSafe) ) car les instances de Type sont partagées entre AppDomains et l'étendue du verrou peut donc inclure du code (par exemple, si ThreadSafe est chargé dans deux AppDomains dans le même processus puis verrouiller sur son instance Type se verrouilleraient mutuellement).

Anti-Patterns et Gotchas

Verrouillage sur une variable allouée / locale

L'une des erreurs lors de l'utilisation de lock est l'utilisation d'objets locaux comme casier dans une fonction. Étant donné que ces instances d'objet local différeront à chaque appel de la fonction, le lock ne fonctionnera pas comme prévu.

List<string> stringList = new List<string>();

public void AddToListNotThreadSafe(string something)
{
    // DO NOT do this, as each call to this method 
    // will lock on a different instance of an Object.
    // This provides no thread safety, it only degrades performance.
    var localLock = new Object();
    lock(localLock)
    {
        stringList.Add(something);
    }
}

// Define object that can be used for thread safety in the AddToList method
readonly object classLock = new object();

public void AddToList(List<string> stringList, string something)
{
    // USE THE classLock instance field to achieve a 
    // thread-safe lock before adding to stringList
    lock(classLock)
    {
        stringList.Add(something);
    }
}

En supposant que le verrouillage restreint l'accès à l'objet de synchronisation lui-même

Si un thread appelle: lock(obj) et un autre thread appelle obj.ToString() deuxième thread ne sera pas bloqué.

object obj = new Object();
 
public void SomeMethod()
{
     lock(obj)
    {
       //do dangerous stuff 
    }
 }

 //Meanwhile on other tread 
 public void SomeOtherMethod()
 {
   var objInString = obj.ToString(); //this does not block
 }

Attend des sous-classes pour savoir quand verrouiller

Parfois, les classes de base sont conçues de telle sorte que leurs sous-classes doivent utiliser un verrou lors de l'accès à certains champs protégés:

public abstract class Base
{
    protected readonly object padlock;
    protected readonly List<string> list;

    public Base()
    {
        this.padlock = new object();
        this.list = new List<string>();
    }

    public abstract void Do();
}

public class Derived1 : Base
{
    public override void Do()
    {
        lock (this.padlock)
        {
            this.list.Add("Derived1");
        }
    }
}

public class Derived2 : Base
{
    public override void Do()
    {
        this.list.Add("Derived2"); // OOPS! I forgot to lock!
    }
}

Il est beaucoup plus sûr d' encapsuler le verrouillage en utilisant une méthode de modèle :

public abstract class Base
{
    private readonly object padlock; // This is now private
    protected readonly List<string> list;

    public Base()
    {
        this.padlock = new object();
        this.list = new List<string>();
    }

    public void Do()
    {
        lock (this.padlock) {
            this.DoInternal();
        }
    }

    protected abstract void DoInternal();
}

public class Derived1 : Base
{
    protected override void DoInternal()
    {
        this.list.Add("Derived1"); // Yay! No need to lock
    }
}

Le verrouillage sur une variable ValueType en boîte ne se synchronise pas

Dans l'exemple suivant, une variable privée est implicitement encadrée car elle est fournie en tant qu'argument d' object à une fonction, s'attendant à ce qu'une ressource de moniteur se verrouille. La boxe se produit juste avant d'appeler la fonction IncInSync, donc l'instance encadrée correspond à un objet de segment différent à chaque appel de la fonction.

public int Count { get; private set; }

private readonly int counterLock = 1;

public void Inc()
{
    IncInSync(counterLock);
}

private void IncInSync(object monitorResource)
{
    lock (monitorResource)
    {
        Count++;
    }
}

La boxe se produit dans la fonction Inc :

BulemicCounter.Inc:
IL_0000:  nop         
IL_0001:  ldarg.0     
IL_0002:  ldarg.0     
IL_0003:  ldfld       UserQuery+BulemicCounter.counterLock
IL_0008:  box         System.Int32**
IL_000D:  call        UserQuery+BulemicCounter.IncInSync
IL_0012:  nop         
IL_0013:  ret         

Cela ne signifie pas qu'un ValueType en boîte ne peut pas être utilisé pour le verrouillage du moniteur:

private readonly object counterLock = 1;

Maintenant, la boxe se produit dans le constructeur, ce qui est bien pour le verrouillage:

IL_0001:  ldc.i4.1    
IL_0002:  box         System.Int32
IL_0007:  stfld       UserQuery+BulemicCounter.counterLock

Utiliser des verrous inutilement lorsqu'une alternative plus sûre existe

Un modèle très courant consiste à utiliser une List ou un Dictionary privé dans une classe de thread thread et à verrouiller chaque fois qu'il est accessible:

public class Cache
{
    private readonly object padlock;
    private readonly Dictionary<string, object> values;

    public WordStats()
    {
        this.padlock = new object();
        this.values = new Dictionary<string, object>();
    }
    
    public void Add(string key, object value)
    {
        lock (this.padlock)
        {
            this.values.Add(key, value);
        }
    }

    /* rest of class omitted */
}

S'il y a plusieurs méthodes qui accèdent au dictionnaire de values , le code peut devenir très long et, plus important encore, le verrouillage tout le temps en cache l' intention . Le verrouillage est également très facile à oublier et le manque de verrouillage peut rendre très difficile la recherche de bogues.

En utilisant un ConcurrentDictionary , nous pouvons éviter de verrouiller complètement:

public class Cache
{
    private readonly ConcurrentDictionary<string, object> values;

    public WordStats()
    {
        this.values = new ConcurrentDictionary<string, object>();
    }
    
    public void Add(string key, object value)
    {
        this.values.Add(key, value);
    }

    /* rest of class omitted */
}

L'utilisation de collections simultanées améliore également les performances car toutes utilisent des techniques sans verrouillage dans une certaine mesure.



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow