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 如果 PulseWait 前调用,则唤醒不起作用(因为没人等)

🧾 五、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.Enterlock 是阻塞式的,没超时参数

  • 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)

posted @ 2025-06-14 19:03  青云Zeo  阅读(131)  评论(0)    收藏  举报