C# Language
Låsa uttalande
Sök…
Syntax
- lås (obj) {}
Anmärkningar
Med hjälp av lock
du styra olika tråders åtkomst till kod i kodblocket. Det används ofta för att förhindra rasförhållanden, till exempel flera trådar som läser och tar bort objekt från en samling. Eftersom låsning tvingar trådar att vänta på att andra trådar lämnar ett kodblock kan det orsaka förseningar som kan lösas med andra synkroniseringsmetoder.
MSDN
Låsenordet markerar ett uttalande block som ett kritiskt avsnitt genom att erhålla låset för ömsesidig uteslutning för ett visst objekt, utföra ett uttalande och sedan släppa låset.
Låsnyckelordet säkerställer att en tråd inte anger ett kritiskt avsnitt av koden medan en annan tråd finns i det kritiska avsnittet. Om en annan tråd försöker ange en låst kod kommer den att vänta, blockera tills objektet släpps.
Bästa praxis är att definiera ett privat objekt att låsa på, eller en privat statisk objektvariabel för att skydda data som är gemensamma för alla fall.
I C # 5.0 och senare lock
är uttalandet motsvarar:
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 och tidigare, lock
är uttalandet motsvarar:
System.Threading.Monitor.Enter(refObject);
try
{
// code
}
finally
{
System.Threading.Monitor.Exit(refObject);
}
Enkel användning
Vanlig användning av lock
är ett kritiskt avsnitt.
I följande exempel ska ReserveRoom
kallas från olika trådar. Synkronisering med lock
är det enklaste sättet att förhindra rasförhållanden här. Metodkroppen är omgiven av lock
som säkerställer att två eller flera trådar inte kan köra den samtidigt.
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
}
}
}
Om en tråd når lock
-ED blocket medan en annan tråd körs inom det kommer den förstnämnda vänta ytterligare för att avsluta blocket.
Bästa praxis är att definiera ett privat objekt att låsa på, eller en privat statisk objektvariabel för att skydda data som är gemensamma för alla fall.
Kasta undantag i ett låsmeddelande
Följande kod släpper låset. Det kommer inte att vara några problem. Bakom kulisserna fungerar try finally
som try finally
lock(locker)
{
throw new Exception();
}
Mer kan ses i C # 5.0-specifikationen :
Ett lock
av formuläret
lock (x) ...
där x
är ett uttryck av en referenstyp , är exakt motsvarande
bool __lockWasTaken = false;
try {
System.Threading.Monitor.Enter(x, ref __lockWasTaken);
...
}
finally {
if (__lockWasTaken) System.Threading.Monitor.Exit(x);
}
förutom att x
bara utvärderas en gång.
Återvänd i ett låsmeddelande
Följande kod släpper låset.
lock(locker)
{
return 5;
}
För en detaljerad förklaring rekommenderas detta SO-svar .
Använda instanser av Objekt för lås
När du använder C #: s inbyggda lock
behövs en instans av någon typ, men dess tillstånd spelar ingen roll. En förekomst av object
är perfekt för detta:
public class ThreadSafe {
private static readonly object locker = new object();
public void SomeThreadSafeMethod() {
lock (locker) {
// Only one thread can be here at a time.
}
}
}
OBS . instanser av Type
bör inte användas för detta (i koden ovan typeof(ThreadSafe)
) eftersom instanser av Type
delas över AppDomains och därför kan låsens omfattning förväntas inkludera kod som det inte borde (t.ex. om ThreadSafe
laddas i två AppDomains i samma process sedan låsa på dess Type
instans skulle ömsesidigt låsa).
Anti-mönster och gotchor
Låsning på en stacktilldelad / lokal variabel
Ett av de felaktigheter som används vid lock
är användningen av lokala objekt som skåp i en funktion. Eftersom dessa lokala objekttillfällen kommer att skilja sig åt varje samtal i funktionen, fungerar inte lock
som förväntat.
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);
}
}
Antagande att låsning begränsar åtkomsten till själva synkroniseringsobjektet
Om en tråd ringer: lock(obj)
och en annan tråd ringer obj.ToString()
andra tråden inte att blockeras.
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
}
Förväntar sig underklasser att veta när de ska låsas
Ibland är basklasser utformade så att deras underklasser krävs för att använda ett lås vid åtkomst till vissa skyddade fält:
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!
}
}
Det är mycket säkrare att kapa in låsning med hjälp av en mallmetod :
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
}
}
Om du låser en variabel i ValueType-rutan synkroniseras inte
I följande exempel är en privat variabel implicit inramad som det levereras som ett object
argument till en funktion, förväntar sig en bildskärm resurs för att låsa på. Boxningen inträffar precis innan du ringer upp IncInSync-funktionen, så den inställda instansen motsvarar ett annat högobjekt varje gång funktionen anropas.
public int Count { get; private set; }
private readonly int counterLock = 1;
public void Inc()
{
IncInSync(counterLock);
}
private void IncInSync(object monitorResource)
{
lock (monitorResource)
{
Count++;
}
}
Boxning sker i Inc
funktionen:
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
Det betyder inte att en boxad ValueType inte kan användas för skärmlåsning alls:
private readonly object counterLock = 1;
Nu sker boxning i konstruktören, vilket är bra att låsa:
IL_0001: ldc.i4.1
IL_0002: box System.Int32
IL_0007: stfld UserQuery+BulemicCounter.counterLock
Att använda lås i onödan när ett säkrare alternativ finns
Ett mycket vanligt mönster är att använda en privat List
eller Dictionary
i en trådsäker klass och låsas varje gång den öppnas:
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 */
}
Om det finns flera metoder för att komma åt values
kan koden bli väldigt lång och, ännu viktigare, om du låser hela tiden döljer dess avsikt . Låsning är också mycket lätt att glömma och brist på korrekt låsning kan orsaka mycket svårt att hitta buggar.
Genom att använda en ConcurrentDictionary
kan vi undvika låsning helt:
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 */
}
Genom att använda samtidiga samlingar förbättras också prestanda eftersom alla till viss del använder låsfria tekniker .