C#中的Monitor
🔍 一、Monitor 是什么?
Monitor 是 C# 中用于实现线程同步的一种机制,属于 System.Threading 命名空间。它内部维护一个“线程锁”,确保多个线程有序访问某个资源(通常是对象)。
可以把它理解为一个“线程红绿灯”:一个线程正在访问资源时,其他线程必须等待。
🧠 二、Monitor 有什么作用?
- 保护临界区代码,防止多个线程同时修改共享资源(如列表、字典、文件等);
- 实现等待-通知机制(比如生产者-消费者模式);
- 比 lock 更灵活:可以显式
Enter/Exit/Wait/Pulse。
🛠️ 三、Monitor 的常见用法
✅ 1. 简单互斥访问(lock 的底层就是 Monitor)
object syncObj = new object();
Monitor.Enter(syncObj);
try
{
// 临界区代码:访问共享资源
}
finally
{
Monitor.Exit(syncObj); // 一定要释放锁
}
✅ 等价于:
lock(syncObj) { // 临界区 }
✅ 2. 等待与通知(Wait / Pulse)
用于一个线程等待另一个线程发信号,典型场景:生产者-消费者。
object syncObj = new object();
Queue<int> queue = new Queue<int>();
void Consumer()
{
lock(syncObj)
{
while (queue.Count == 0)
{
Monitor.Wait(syncObj); // 等待有数据
}
int item = queue.Dequeue();
Console.WriteLine("消费了:" + item);
}
}
void Producer()
{
lock(syncObj)
{
queue.Enqueue(42);
Monitor.Pulse(syncObj); // 通知消费者线程醒来
}
}
📌 说明:
Monitor.Wait(syncObj):当前线程 释放锁并等待;Monitor.Pulse(syncObj):唤醒等待该锁的某个线程;Monitor.PulseAll(syncObj):唤醒所有等待的线程。
⚠️ 四、使用 Monitor 要注意什么?
| 注意点 | 说明 |
|---|---|
| 必须成对使用 Enter/Exit | 否则程序会死锁或抛异常。建议用 try-finally 保证 Exit 一定执行 |
| Wait 必须在锁内调用 | Monitor.Wait() 必须在 lock(syncObj) 或 Monitor.Enter() 中使用 |
| 使用 Pulse 后,被唤醒的线程不会立刻执行 | 它会排队等锁,直到当前线程退出锁定块 |
| 避免死锁 | 不要在一个锁中等待另一个锁释放(A 等 B,B 等 A) |
| 避免遗漏 Pulse | 如果 Pulse 在 Wait 前调用,则唤醒不起作用(因为没人等) |
🧾 五、Monitor 与 lock 的关系
| 功能 | lock |
Monitor |
|---|---|---|
| 自动加/释放锁 | ✅ | ❌(需要写 Enter/Exit) |
| 支持等待/通知 | ❌ | ✅(Wait/Pulse) |
| 灵活控制线程同步 | ❌ | ✅ |
| 简单互斥锁 | ✅ | ✅ |
通常:
- 如果只需要简单线程互斥:用
lock - 如果需要线程间协调唤醒:用
Monitor
📚 六、实际应用场景
| 场景 | 用 Monitor 的方式 |
|---|---|
| 数据写入中途不允许 flush | 用 _syncRoot + Monitor.Enter/Exit |
| 一个线程生产数据,另一个消费数据 | Wait + Pulse 实现等待通知 |
| 等待某个标志变为 true | while (!condition) Monitor.Wait(...) |
✅ 总结口诀
✅ “lock 是语法糖,Monitor 是核心 API”
✅Monitor.Enter/Exit用于加锁解锁
✅Monitor.Wait会释放锁并等待
✅Monitor.Pulse唤醒等待线程
✅ 用try...finally保证锁释放
✅ Wait 和 Pulse 必须在锁内部使用
补充内容
1. 获取锁是什么意思?
获取锁就是“获得对某个共享资源的独占访问权”,确保在同一时间,只有一个线程能访问那个资源(代码块、对象、文件等)。
- 线程要执行“临界区”代码前,必须先“获取锁”。
- 如果锁被别的线程持有了,当前线程就被阻塞等待,直到锁被释放,才能继续执行。
2. “如果等待5秒或10秒没拿到锁就放弃”的场景
这是个带超时的锁等待,即:
- 线程尝试获取锁,
- 如果锁空闲,马上获得,执行代码;
- 如果锁被占用,等待一段时间(比如5秒),
- 超时还没获得锁,则放弃等待,做其他事情。
3. 传统 Monitor.Enter 和 lock 是阻塞式的,没超时参数
Monitor.Enter(obj)是无限等待直到拿到锁。lock(obj)是Monitor.Enter的语法糖。
它们不支持带超时的等待。
4. 如何用 Monitor.TryEnter 实现带超时的锁等待?
Monitor 提供了 TryEnter 方法,可以尝试获取锁,带超时时间,示例:
object syncObj = new object();
bool lockTaken = false;
try
{
// 尝试获取锁,最多等待5000毫秒(5秒)
lockTaken = Monitor.TryEnter(syncObj, 5000);
if (lockTaken)
{
// 成功获得锁,执行临界区代码
Console.WriteLine("获取锁成功,执行操作");
}
else
{
// 超时未获取锁,执行其他逻辑
Console.WriteLine("获取锁超时,放弃等待,执行其他操作");
}
}
finally
{
if (lockTaken)
{
Monitor.Exit(syncObj); // 释放锁
}
}
5. 说明
-
Monitor.TryEnter(object obj, int millisecondsTimeout):- 试图获取
obj上的锁,等待最多millisecondsTimeout毫秒; - 返回
true表示成功拿到锁; - 返回
false表示超时未拿到锁。
- 试图获取
-
用
try...finally确保锁释放。 -
不用
lock,必须用Monitor的方式写。
6. 用这个可以实现:
- “尝试加锁,最多等5秒”;
- 如果5秒内没锁,转而做别的事情。
7. 补充:如果用 lock 只能无限等待,不支持超时!
总结
| 需求 | 推荐做法 |
|---|---|
| 获取锁,阻塞等待 | lock(obj) 或 Monitor.Enter |
| 获取锁,带超时等待 | Monitor.TryEnter(obj, timeout) |

浙公网安备 33010602011771号