lock 锁详解
一、先解释:lock 是什么?
lock 是 C# 中用于"多线程同步"的关键字,它保证同一时刻只有一个线程能进入被锁住的代码块。
lock (锁对象)
{
// 临界区:同一时刻只允许一个线程执行这里
}
核心概念:
| 概念 | 说明 |
|---|---|
| 锁对象 | lock 括号里的对象,作为"门锁"的标识 |
| 临界区 | 被 lock 包裹的代码块,需要互斥保护的代码 |
| 互斥 | 一个线程进入后,其他线程必须等待,直到它退出 |
执行流程:
二、为什么需要锁?(问题背景)
多线程并发访问共享数据时,会产生"竞态条件"(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保证释放锁),不会死锁在异常上。- 锁对象必须是引用类型(
object、string等),不能是值类型。 - 锁对象通常用
private readonly object声明,不对外暴露。
四、lock 的注意事项与最佳实践
- 锁对象要私有:不要用
lock(this)或lock(typeof(类)),外部可能拿到同一对象造成意外阻塞。 - 锁粒度要小:只锁真正需要保护的代码,不要在锁内做耗时操作(如文件 IO、网络请求)。
- 不要在锁内调用外部回调:可能死锁。
lock不是万能的:它只保护"代码块",不保护"数据"。所有访问共享数据的代码都必须用同一把锁。- 替代方案:
ConcurrentDictionary、Interlocked、ReaderWriterLockSlim等,按需选择。
七、总结
lock= 多线程互斥锁,保证同一时刻只有一个线程进入临界区,防止竞态条件。- 锁分离设计让业务与日志互不阻塞,提高并发度。
- 事件广播放在锁外,避免持锁调用外部回调导致死锁。

1154

被折叠的 条评论
为什么被折叠?



