lock 语句 - 确保对共享资源的独占访问

lock 语句获取给定对象的互斥锁,执行语句块,然后释放锁。 锁定时,持有锁的线程可以再次获取并释放锁。 阻止任何其他线程获取锁,并等待锁释放。 该 lock 语句可确保在任意时刻最多只执行一个线程的正文。

lock 语句采用以下形式:

lock (x)
{
    // Your code...
}

变量x是类型或引用类型的表达式System.Threading.Lock。 当 x 在编译时已知为类型 System.Threading.Lock时,它完全等效于:

using (x.EnterScope())
{
    // Your code...
}

返回 Lock.EnterScope() 的对象包括 ref struct 一个 Dispose() 方法。 生成的 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 中引发异常,也会释放锁。

不能在await语句正文lock中使用表达式。

准则

从 .NET 9 和 C# 13 开始,锁定类型的专用对象实例 System.Threading.Lock 以获取最佳性能。 此外,如果已知 Lock 对象被强制转换为另一种类型并锁定,编译器会发出警告。 如果使用旧版的 .NET 和 C#,请锁定不用于其他用途的专用对象实例。 避免对不同的共享资源使用相同的锁对象实例,因为这可能会导致死锁或锁争用。 具体而言,请避免将以下实例用作锁对象:

  • this,因为调用方也可能锁定 this
  • Type 实例,因为它们可能由 typeof 运算符或反射获取。
  • 字符串实例,包括字符串文本,因为它们可能是 实习生

尽可能短地保留锁,以减少锁争用。

示例:

以下示例定义一个Account类,该类通过锁定专用balanceLock实例来同步对其专用balance字段的访问。 使用同一实例进行锁定可确保两个不同的线程不能同时调用DebitCredit方法更新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 语句部分。

另请参阅