Suche…


Syntax

  • sperren (obj) {}

Bemerkungen

Mit der lock Anweisung können Sie den Zugriff verschiedener Threads auf Code innerhalb des Codeblocks steuern. Sie wird normalerweise verwendet, um Race-Bedingungen zu verhindern, z. B. mehrere Threads, die Elemente aus einer Sammlung lesen und entfernen. Da das Sperren dazu führt, dass Threads warten, bis andere Threads einen Codeblock verlassen, kann dies zu Verzögerungen führen, die mit anderen Synchronisationsmethoden behoben werden könnten.

MSDN

Das Schlüsselwort lock kennzeichnet einen Anweisungsblock als einen kritischen Abschnitt, indem die gegenseitige Ausschlusssperre für ein bestimmtes Objekt abgerufen, eine Anweisung ausgeführt und dann die Sperre freigegeben wird.

Das Schlüsselwort lock stellt sicher, dass ein Thread keinen kritischen Codeabschnitt eingibt, während sich ein anderer Thread im kritischen Abschnitt befindet. Wenn ein anderer Thread versucht, einen gesperrten Code einzugeben, wartet er, blockiert, bis das Objekt freigegeben wird.

Es empfiehlt sich, ein privates Objekt zum Sperren oder eine private statische Objektvariable zu definieren, um Daten zu schützen, die in allen Instanzen gemeinsam sind.


In C # 5.0 und höher entspricht die lock :

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

Für C # 4.0 und früher entspricht die lock :

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

Einfache Benutzung

Die häufige Verwendung von lock ist ein kritischer Abschnitt.

Im folgenden Beispiel soll ReserveRoom aus verschiedenen Threads aufgerufen werden. Die Synchronisierung mit der lock ist der einfachste Weg, um einen Race-Zustand zu verhindern. Der Methodenkörper ist mit einer lock umgeben lock die sicherstellt, dass zwei oder mehr Threads sie nicht gleichzeitig ausführen können.

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

Wenn ein Thread erreicht lock -ed Block während eines anderen Thread innerhalb es läuft, wird die ehemaligen andere warten , um den Block zu verlassen.

Es empfiehlt sich, ein privates Objekt zum Sperren oder eine private statische Objektvariable zu definieren, um Daten zu schützen, die für alle Instanzen gelten.

Ausnahme in einer Sperranweisung

Der folgende Code gibt die Sperre frei. Es wird kein Problem geben. Hinter den Kulissen funktioniert das Lock-Statement als try finally

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

Weitere Informationen finden Sie in der C # 5.0-Spezifikation :

Eine lock des Formulars

lock (x) ...

wobei x ein Ausdruck eines Referenztyps ist , ist genau äquivalent zu

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

mit der Ausnahme, dass x nur einmal ausgewertet wird.

Rückgabe in einer Sperranweisung

Der folgende Code gibt die Sperre frei.

lock(locker)
{
    return 5;
}

Für eine detaillierte Erklärung wird diese SO-Antwort empfohlen.

Instanzen von Object für die Sperre verwenden

Wenn Sie die eingebaute lock Anweisung von C # verwenden, ist eine Instanz eines bestimmten Typs erforderlich, der Status ist jedoch unerheblich. Eine Instanz eines object ist dafür perfekt:

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 . Instanzen von Type sollten dafür nicht verwendet werden (im obigen Code von typeof(ThreadSafe) ), da Instances von Type AppDomains gemeinsam genutzt werden und der Umfang der Sperre erwartungsgemäß Code enthalten kann, in den er nicht sollte (z. B. wenn ThreadSafe geladen wird zwei AppDomains in demselben Prozess, die dann für ihre Type Instanz gesperrt werden, sperren sich gegenseitig).

Anti-Patterns und Gotchas

Sperren für eine stapelzugeordnete / lokale Variable

Einer der Fehler bei der Verwendung von lock ist die Verwendung lokaler Objekte als Schließfach in einer Funktion. Da diese lokalen Objektinstanzen bei jedem Aufruf der Funktion unterschiedlich sind, wird die lock nicht wie erwartet ausgeführt.

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);
    }
}

Angenommen, das Sperren schränkt den Zugriff auf das Synchronisierungsobjekt selbst ein

Wenn ein Thread aufruft: lock(obj) und ein anderer Thread ruft obj.ToString() zweite Thread nicht blockiert.

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
 }

Erwarten, dass Unterklassen wissen, wann gesperrt werden soll

Manchmal werden Basisklassen so konzipiert, dass ihre Unterklassen beim Zugriff auf bestimmte geschützte Felder eine Sperre verwenden müssen:

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!
    }
}

Es ist viel sicherer, das Sperren mithilfe einer Vorlagenmethode zu kapseln :

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

Das Sperren einer ValueType-Variablen mit Box wird nicht synchronisiert

Im folgenden Beispiel wird eine private Variable implizit eingebettet, da sie als object für eine Funktion bereitgestellt wird und erwartet, dass eine Überwachungsressource gesperrt wird. Das Boxen tritt direkt vor dem Aufrufen der IncInSync-Funktion auf, sodass die Box-Instanz bei jedem Aufruf der Funktion einem anderen Heap-Objekt entspricht.

public int Count { get; private set; }

private readonly int counterLock = 1;

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

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

Boxen erfolgt in der Inc Funktion:

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         

Das bedeutet nicht, dass ein geschachtelter ValueType überhaupt nicht für das Sperren des Monitors verwendet werden kann:

private readonly object counterLock = 1;

Das Boxen wird jetzt im Konstruktor ausgeführt, was für das Sperren geeignet ist:

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

Sperren unnötig verwenden, wenn eine sicherere Alternative vorhanden ist

Ein sehr verbreitetes Muster ist die Verwendung einer privaten List oder eines privaten Dictionary in einer sicheren Klasse für den Thread und die Sperre bei jedem Zugriff:

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 */
}

Wenn mehrere Methoden auf das values zugreifen, kann der Code sehr lang werden und, was wichtiger ist, die permanente Sperrung verdeckt seine Absicht . Die Verriegelung ist auch sehr leicht zu vergessen und das Fehlen einer ordnungsgemäßen Verriegelung kann dazu führen, dass Fehler nur schwer gefunden werden.

Durch die Verwendung eines ConcurrentDictionary können Sie das Sperren vollständig vermeiden:

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 */
}

Die Verwendung von gleichzeitigen Auflistungen verbessert auch die Leistung, da alle bis zu einem gewissen Grad sperrenfreie Techniken verwenden .



Modified text is an extract of the original Stack Overflow Documentation
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow