lock 语句 - 确保对共享资源的独占访问权限
lock
语句获取给定对象的互斥 lock,执行语句块,然后释放 lock。 持有 lock 时,持有 lock 的线程可以再次获取并释放 lock。 阻止任何其他线程获取 lock 并等待释放 lock。 lock
语句可确保在任何时候最多只有一个线程执行其主体。
lock
语句采用以下形式:
lock (x)
{
// Your code...
}
变量 x
是 System.Threading.Lock 类型或引用类型的表达式。 当 x
在编译时已知属于类型 System.Threading.Lock 时,它完全等效于:
using (x.EnterScope())
{
// Your code...
}
Lock.EnterScope() 返回的对象是包括一个 Dispose()
方法的 ref struct
。 生成的 using
语句可确保即使 lock
语句正文引发异常,也会释放范围。
否则,该 lock
语句完全等效于:
object __lockObj = x;
bool __lockWasTaken = false;
try
{
System.Threading.Monitor.Enter(__lockObj, ref __lockWasTaken);
// Your code...
}
finally
{
if (__lockWasTaken) System.Threading.Monitor.Exit(__lockObj);
}
由于该代码使用 try-finally
语句,因此即使在 lock
语句的正文中引发异常,也会释放 lock。
在 lock
语句的正文中不能使用 await
表达式。
准则
从 .NET 9 和 C# 13 开始,锁定 System.Threading.Lock 类型的专用对象实例以获取最佳性能。 此外,如果已知的 Lock
对象被强制转换为另一种类型并锁定,编译器会发出警告。 如果使用旧版的 .NET 和 C#,请锁定不用于其他用途的专用对象实例。 避免对不同的共享资源使用相同的 lock 对象实例,因为这可能导致死锁或锁争用。 具体而言,请避免将以下实例用作 lock 对象:
尽可能缩短持有锁的时间,以减少锁争用。
示例
以下示例定义了一个 Account
类,该类通过锁定专用的 balanceLock
实例来同步对其专用 balance
字段的访问。 使用同一实例进行锁定可确保两个不同的线程不能同时调用 Debit
或 Credit
方法更新 balance
字段。 此示例使用 C# 13 和新 Lock
对象。 如果使用较旧版本的 C# 或较旧的 .NET 库,请锁定 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# 语言规范
有关详细信息,请参阅 C# 语言规范中的 lock 语句部分。