c# lock 和 Monitor.Enter/Exit 在IL层面有什么区别

来源:这里教程网 时间:2026-02-21 17:38:27 作者:

lock 在 IL 中会自动生成 try-finally 包裹

直接说结论:

lock(obj) { ... }
编译后,IL 中**必然生成
try
块 +
finally
块**,其中
Monitor.Exit
被强制放在
finally
里;而手写
Monitor.Enter
/
Monitor.Exit
若不手动加
try-finally
,就**完全没保障**——一旦
Enter
后、执行业务逻辑前抛异常(比如线程被中止、OOM、StackOverflow),
Exit
就永远不会执行,锁永久泄露。

lock
的 IL 等效于:
Monitor.Enter(obj);
try {
  // your code
}
finally {
  Monitor.Exit(obj);
}
手写
Monitor.Enter
/
Monitor.Exit
的常见错误写法(危险!):
Monitor.Enter(obj);
DoSomething(); // ← 这里崩了?锁就卡死了
Monitor.Exit(obj); // ← 永远不会执行
C# 4.0+ 提供了更安全的手写方式:
Monitor.Enter(obj, ref lockTaken)
,配合
if (lockTaken) Monitor.Exit(obj)
,但依然要你自己写
try-finally
才算完整

lock 只支持引用类型,Monitor.Enter 可“误用”值类型

lock
语法强制要求锁对象是引用类型,编译器会报错:
CS0185: 'lock' statement operand must be of a reference type
。而
Monitor.Enter
接收
object
参数,传入值类型(如
int
struct
)会触发装箱——每次调用都产生**新对象实例**,导致锁失效(因为不是同一个对象)。

下面这段代码**根本不同步**:
int locker = 42;
Monitor.Enter(locker); // ← 装箱成新 object
// ... 业务逻辑
Monitor.Exit(locker); // ← 再次装箱,是另一个 object!
lock(42)
直接编译失败,反而帮你避开了这个坑
真正安全的锁对象必须是**同一个引用**:静态
readonly object
、私有字段、
typeof(MyClass)
(慎用,有跨程序集风险)

Monitor 提供 lock 没有的超时和等待通知能力

lock
是纯阻塞式互斥,要么拿到锁立刻进,要么一直等。而
Monitor
类暴露了更底层的能力:

Monitor.TryEnter(obj, timeoutMs)
:带超时获取锁,避免死等 ——
lock
完全不支持
Monitor.Wait(obj)
+
Monitor.Pulse(obj)
/
PulseAll
:实现线程间条件等待(比如生产者-消费者),
lock
语法无法表达这种协作语义
这些方法**必须在已持有该对象锁的前提下调用**,否则抛
SynchronizationLockException

容易被忽略的细节:lock 对象的可见性与生命周期

无论用

lock
还是
Monitor
,锁失效往往不是语法问题,而是对象本身出错了:

锁对象被设为
null
NullReferenceException
lock(null)
编译期不报错,运行时报)
锁对象是局部变量或每次 new 出来的 → 每次锁的都不是同一个东西,形同虚设
lock(this)
lock(typeof(...))
→ 外部代码可能也锁它,引发意外争用或死锁
锁对象被 GC 回收(极少见,但若锁对象无强引用且被弱引用持有,可能触发 Finalize 并发问题)

真正难的从来不是“怎么写 lock”,而是“锁谁”和“锁多久”。Monitor 的裸 API 把这些责任全摊开给你,lock 则帮你盖住了一层——但盖不住设计缺陷。

相关推荐