C# Language
Lock-Anweisung
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 .