A zárolási utasítás – kizárólagos hozzáférés biztosítása megosztott erőforráshoz
Az lock
utasítás beszerzi egy adott objektum kölcsönös kizárási zárolását, végrehajt egy utasításblokkot, majd feloldja a zárolást. A zárolás megtartása közben a zárolást tartalmazó szál ismét beszerezheti és feloldhatja a zárolást. Minden más szál le van tiltva a zárolás beszerzésében, és megvárja a zárolás feloldását. Az lock
utasítás biztosítja, hogy legfeljebb egy szál hajtja végre a törzsét bármikor.
Az lock
utasítás a következő formában történik:
lock (x)
{
// Your code...
}
A változó x
típuskifejezés System.Threading.Lock vagy referenciatípus. Ha x
a fordítási időben ismert, hogy a típus System.Threading.Lock, akkor pontosan egyenértékű a következő értékeivel:
using (x.EnterScope())
{
// Your code...
}
A visszaadott Lock.EnterScope() objektum egy ref struct
metódust Dispose()
tartalmazó objektum. A létrehozott using
utasítás akkor is biztosítja a hatókör felszabadítását, ha kivételt okoz az lock
utasítás törzse.
Ellenkező esetben az lock
utasítás pontosan egyenértékű a következő értékekkel:
object __lockObj = x;
bool __lockWasTaken = false;
try
{
System.Threading.Monitor.Enter(__lockObj, ref __lockWasTaken);
// Your code...
}
finally
{
if (__lockWasTaken) System.Threading.Monitor.Exit(__lockObj);
}
Mivel a kód utasítást try-finally
használ, a zárolás akkor is ki lesz oldva, ha kivételt ad egy lock
utasítás törzsében.
A kifejezés nem használható az await
utasítás törzsébenlock
.
Irányelvek
A .NET 9-től és a C# 13-tól kezdve a legjobb teljesítmény érdekében zároljon egy ilyen típusú dedikált objektumpéldányt System.Threading.Lock . Emellett a fordító figyelmeztetést ad ki, ha egy ismert Lock
objektumot egy másik típusba öntöttek és zároltak. Ha a .NET és a C# régebbi verzióját használja, zároljon egy dedikált objektumpéldányt, amely más célra nem használható. Ne használja ugyanazt a zárolási objektumpéldányt a különböző megosztott erőforrásokhoz, mert az holtpontot vagy zárolási versengést eredményezhet. Különösen kerülje a következő példányok zárolási objektumként való használatát:
this
, mivel a hívók is zárolhatnakthis
.- Type példányokat, mivel azokat az operátor vagy a tükröződés típusával szerezheti be.
- sztringpéldányok, beleértve a sztringkonstansokat is, mivel ezek internálhatók.
A zárolási versengés csökkentése érdekében a lehető legrövidebb ideig tartsa lenyomva a zárolást.
Példa
Az alábbi példa egy olyan osztályt Account
határoz meg, amely egy dedikált balanceLock
példány zárolásával szinkronizálja a privát balance
mezőhöz való hozzáférést. Ha ugyanazt a példányt használja a zároláshoz, az biztosítja, hogy két különböző szál ne tudja egyszerre meghívni a balance
mezőt vagy Credit
metódusokatDebit
. A minta a C# 13-at és az új Lock
objektumot használja. Ha a C# régebbi verzióját vagy egy régebbi .NET-tárat használ, zárolja a példányt object
.
using System;
using System.Threading.Tasks;
public class Account
{
// Use `object` in versions earlier than C# 13
private readonly System.Threading.Lock _balanceLock = new();
private decimal _balance;
public Account(decimal initialBalance) => _balance = initialBalance;
public decimal Debit(decimal amount)
{
if (amount < 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "The debit amount cannot be negative.");
}
decimal appliedAmount = 0;
lock (_balanceLock)
{
if (_balance >= amount)
{
_balance -= amount;
appliedAmount = amount;
}
}
return appliedAmount;
}
public void Credit(decimal amount)
{
if (amount < 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "The credit amount cannot be negative.");
}
lock (_balanceLock)
{
_balance += amount;
}
}
public decimal GetBalance()
{
lock (_balanceLock)
{
return _balance;
}
}
}
class AccountTest
{
static async Task Main()
{
var account = new Account(1000);
var tasks = new Task[100];
for (int i = 0; i < tasks.Length; i++)
{
tasks[i] = Task.Run(() => Update(account));
}
await Task.WhenAll(tasks);
Console.WriteLine($"Account's balance is {account.GetBalance()}");
// Output:
// Account's balance is 2000
}
static void Update(Account account)
{
decimal[] amounts = [0, 2, -3, 6, -2, -1, 8, -5, 11, -6];
foreach (var amount in amounts)
{
if (amount >= 0)
{
account.Credit(amount);
}
else
{
account.Debit(Math.Abs(amount));
}
}
}
}
C# nyelvspecifikáció
További információ: A C# nyelv specifikációjának zárolási utasítás szakasza.