Zoeken…


Syntaxis

  • lock (obj) {}

Opmerkingen

Met de lock kunt u de toegang van verschillende threads tot code binnen het codeblok beheren. Het wordt vaak gebruikt om raceomstandigheden te voorkomen, bijvoorbeeld dat meerdere threads items lezen en verwijderen uit een verzameling. Omdat vergrendeling threads dwingt te wachten tot andere threads een codeblok verlaten, kan dit vertragingen veroorzaken die kunnen worden opgelost met andere synchronisatiemethoden.

MSDN

Het vergrendelingssleutelwoord markeert een instructieblok als een kritieke sectie door de vergrendeling voor wederzijdse uitsluiting voor een bepaald object te verkrijgen, een instructie uit te voeren en vervolgens de vergrendeling op te heffen.

Het lock-sleutelwoord zorgt ervoor dat de ene thread geen kritisch gedeelte van de code binnengaat terwijl een andere thread zich in de kritieke sectie bevindt. Als een andere thread probeert een vergrendelde code in te voeren, wacht deze blok totdat het object wordt vrijgegeven.

Best practice is om een privé-object te vergrendelen op, of een private static object variabele te beschermen data voor alle gevallen te definiëren.


In C # 5.0 en hoger, het lock statement is gelijk aan:

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

Voor C # 4.0 en eerdere versies, het lock statement is gelijk aan:

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

Eenvoudig gebruik

Gemeenschappelijk gebruik van lock is een kritieke sectie.

In het volgende voorbeeld zou ReserveRoom vanuit verschillende threads worden aangeroepen. Synchronisatie met lock is de eenvoudigste manier om racecondities hier te voorkomen. Methode lichaam is omgeven met lock die ervoor zorgt dat twee of meer threads het niet tegelijkertijd kunnen uitvoeren.

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

Als een rode draad bereikt lock -ed blok, terwijl een andere thread wordt uitgevoerd binnen het, zal de voormalige wachten nog om het blok te verlaten.

De beste praktijk is om een privéobject te vergrendelen of een statische privéobjectvariabele om gegevens te beschermen die voor alle instanties gelden.

Uitzondering gooien in een slotverklaring

De volgende code zal het slot ontgrendelen. Er zal geen probleem zijn. Achter de schermen werkt de slotverklaring als try finally

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

Meer is te zien in de C # 5.0-specificatie :

Een lock verklaring van het formulier

lock (x) ...

waar x een uitdrukking van een referentietype is , is precies gelijk aan

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

behalve dat x slechts eenmaal wordt geëvalueerd.

Keer terug in een slotverklaring

De volgende code zal het slot ontgrendelen.

lock(locker)
{
    return 5;
}

Voor een gedetailleerde uitleg wordt dit SO-antwoord aanbevolen.

Exemplaren van Object gebruiken voor vergrendeling

Bij gebruik van de ingebouwde lock instructie van C # is een instantie van een bepaald type nodig, maar de status doet er niet toe. Een instantie van object is hiervoor perfect:

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 . exemplaren van Type moeten hiervoor niet worden gebruikt (in de bovenstaande code type van typeof(ThreadSafe) ) omdat exemplaren van Type worden gedeeld tussen AppDomains en dus de omvang van het slot naar verwachting code kan bevatten die het niet zou moeten zijn (bijv. als ThreadSafe is geladen in twee AppDomains in hetzelfde proces dan vergrendeling op de Type instantie zouden elkaar vergrendelen).

Antipatronen en gotcha's

Vergrendelen op een stapel toegewezen / lokale variabele

Een van de denkfouten bij het gebruik van lock is het gebruik van lokale objecten als locker in een functie. Aangezien deze lokale objectinstanties verschillen bij elke aanroep van de functie, werkt lock niet zoals verwacht.

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

Ervan uitgaande dat vergrendeling de toegang tot het synchronisatieobject zelf beperkt

Als een thread aanroept: lock(obj) en een andere thread aanroept obj.ToString() tweede thread wordt niet geblokkeerd.

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
 }

Verwacht dat subklassen weten wanneer ze moeten worden vergrendeld

Soms zijn basisklassen zo ontworpen dat hun subklassen een slot moeten gebruiken bij toegang tot bepaalde beschermde velden:

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

Het is veel veiliger om vergrendeling in te kapselen met behulp van een sjabloonmethode :

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

Vergrendelen op een in een vak geplaatste ValueType-variabele wordt niet gesynchroniseerd

In het volgende voorbeeld wordt een privévariabele impliciet in een doos geplaatst omdat deze als een object aan een functie wordt geleverd, in de verwachting dat een monitorresource wordt vergrendeld. Het boksen gebeurt net voordat de IncInSync-functie wordt aangeroepen, dus de boxed-instantie komt overeen met een ander heap-object elke keer dat de functie wordt aangeroepen.

public int Count { get; private set; }

private readonly int counterLock = 1;

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

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

Boksen vindt plaats in de Inc functie:

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         

Het betekent niet dat een in de doos geplaatst ValueType helemaal niet kan worden gebruikt voor monitorvergrendeling:

private readonly object counterLock = 1;

Nu vindt boksen plaats in de constructor, wat prima is voor vergrendeling:

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

Onnodig gebruik van sloten wanneer er een veiliger alternatief bestaat

Een veel voorkomend patroon is om een List of Dictionary in een thread veilige klasse te gebruiken en te vergrendelen telkens wanneer deze wordt geopend:

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

Als er meerdere methoden toegang tot values woordenboek, kan de code erg lang te krijgen en, nog belangrijker, het vergrendelen van de hele tijd verduistert haar intentie. Vergrendelen is ook heel gemakkelijk te vergeten en een gebrek aan juiste vergrendeling kan ervoor zorgen dat zeer moeilijk te vinden bugs zijn.

Door een ConcurrentDictionary , kunnen we voorkomen dat ze volledig worden vergrendeld:

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

Het gebruik van gelijktijdige collecties verbetert ook de prestaties omdat ze allemaal tot op zekere hoogte lock-free technieken gebruiken.



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow