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 .



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow