C#中的lock锁

lock 锁详解


一、先解释:lock 是什么?

lock 是 C# 中用于"多线程同步"的关键字,它保证同一时刻只有一个线程能进入被锁住的代码块

lock (锁对象)
{
    // 临界区:同一时刻只允许一个线程执行这里
}

核心概念:

概念说明
锁对象lock 括号里的对象,作为"门锁"的标识
临界区lock 包裹的代码块,需要互斥保护的代码
互斥一个线程进入后,其他线程必须等待,直到它退出

执行流程:

线程1 到达 lock

锁是否被占用?

线程1 获取锁, 进入临界区

线程1 执行临界区代码

线程1 释放锁

线程2 获取锁, 进入

线程2 阻塞等待


二、为什么需要锁?(问题背景)

多线程并发访问共享数据时,会产生"竞态条件"(Race Condition),导致数据不一致。

通用示例 1:银行账户并发取款(经典问题)
class BankAccount
{
    private int _balance = 1000;

    // 没有锁:两个线程同时取款会出问题
    public void Withdraw(int amount)
    {
        if (_balance >= amount)          // ① 检查余额
        {
            Thread.Sleep(10);            // 模拟耗时(放大竞态窗口)
            _balance -= amount;          // ② 扣款
        }
    }
}

问题:线程 A 和线程 B 同时取 800 元:

  • A 执行①:_balance=1000 >= 800
  • B 执行①:_balance=1000 >= 800 ✅(还没被 A 扣)
  • A 执行②:_balance = 200
  • B 执行②:_balance = 200错误! 应该扣两次变 -600,或第二次失败)

加锁修复:

class BankAccount
{
    private readonly object _lock = new();
    private int _balance = 1000;

    public void Withdraw(int amount)
    {
        lock (_lock)                     // 同一时刻只有一个线程能进来
        {
            if (_balance >= amount)
            {
                Thread.Sleep(10);
                _balance -= amount;
            }
        }
    }
}

现在 A 和 B 会排队执行:A 先扣完(余额 200),B 再检查发现 200 < 800,拒绝取款。✅

通用示例 2:多线程累加计数器
int counter = 0;
var tasks = new List<Task>();

// 1000 个任务同时 counter++
for (int i = 0; i < 1000; i++)
{
    tasks.Add(Task.Run(() => counter++));
}
Task.WaitAll(tasks.ToArray());
Console.WriteLine(counter);   // 结果可能不是 1000!(竞态导致丢失更新)

counter++ 实际是三步:读值 → 加一 → 写回。多线程并发时,两个线程可能同时读到同一个值,各自加一写回,导致只加了一次

加锁修复:

object lockObj = new();
int counter = 0;

for (int i = 0; i < 1000; i++)
{
    tasks.Add(Task.Run(() =>
    {
        lock (lockObj) { counter++; }   // 串行化,保证结果 = 1000
    }));
}

三、lock 的语法细节

lock (lockObject)
{
    // 临界区
}

等价于(编译器展开):

Monitor.Enter(lockObject);
try
{
    // 临界区
}
finally
{
    Monitor.Exit(lockObject);   // 无论是否异常,都释放锁
}

关键点:

  • lock 会自动处理异常情况(finally 保证释放锁),不会死锁在异常上。
  • 锁对象必须是引用类型objectstring 等),不能是值类型。
  • 锁对象通常用 private readonly object 声明,不对外暴露

四、lock 的注意事项与最佳实践

  1. 锁对象要私有:不要用 lock(this)lock(typeof(类)),外部可能拿到同一对象造成意外阻塞。
  2. 锁粒度要小:只锁真正需要保护的代码,不要在锁内做耗时操作(如文件 IO、网络请求)。
  3. 不要在锁内调用外部回调:可能死锁。
  4. lock 不是万能的:它只保护"代码块",不保护"数据"。所有访问共享数据的代码都必须用同一把锁。
  5. 替代方案ConcurrentDictionaryInterlockedReaderWriterLockSlim 等,按需选择。

七、总结

  • lock = 多线程互斥锁,保证同一时刻只有一个线程进入临界区,防止竞态条件。
  • 锁分离设计让业务与日志互不阻塞,提高并发度。
  • 事件广播放在锁外,避免持锁调用外部回调导致死锁。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值