多线程六脉神剑第二剑:监视器锁 (Monitor)

1、举栗子🌰

场景:公司的唯一打印机

打印机是共享资源

  • Monitor.Enter() 相当于 拿到打印权限并关门

  • Monitor.Exit() 相当于 打印完成并开门

  • Monitor.Wait() 相当于 暂时离开但保留排队位置

  • Monitor.Pulse() 相当于 通知下一个人可以准备了

2、Monitor 的本质

2.1 对象头中的秘密

每个 .NET 对象内部都有一个对象头,其中包含同步块索引:

// 概念性结构 - 实际在 CLR 中实现
class ObjectHeader 
{
    SyncBlockIndex syncBlock;  // 同步块索引
    TypeHandle typeHandle;     // 类型信息
    // ... 其他字段
}

同步块(SyncBlock) 才是真正的锁信息容器:

class SyncBlock 
{
    Thread ownerThread;        // 当前拥有锁的线程
    int recursionCount;        // 重入计数
    WaitQueue waitQueue;       // 等待队列
    // ... 其他同步信息
}

2.2 对象与锁的关联

object lockObj = new object(); // 任何引用类型对象都可以作为锁

// 当你执行 lock(lockObj) 时:
// 1. 检查 lockObj 的对象头中的同步块索引
// 2. 如果为0,创建新的同步块并关联
// 3. 如果已有同步块,检查锁状态
// 4. 根据情况获取锁或进入等待

3、核心工作原理

3.1 锁的获取流程

// 伪代码展示 Monitor.Enter 的内部逻辑
bool MonitorEnter(object obj, ref bool lockTaken)
{
    // 第一步:快速路径 - 无竞争情况
    if (obj.SyncBlockIndex == 0) 
    {
        // 使用原子操作设置同步块
        if (Interlocked.CompareExchange(ref obj.SyncBlockIndex, CreateSyncBlock(), 0) == 0)
        {
            obj.SyncBlock.OwnerThread = CurrentThread;
            obj.SyncBlock.RecursionCount = 1;
            lockTaken = true;
            return true;
        }
    }
    
    // 第二步:检查是否已由当前线程持有(递归锁)
    if (obj.SyncBlock.OwnerThread == CurrentThread)
    {
        obj.SyncBlock.RecursionCount++;
        lockTaken = true;
        return true;
    }
    
    // 第三步:慢速路径 - 需要等待
    return SlowPathMonitorEnter(obj, ref lockTaken);
}

3.2 锁的释放流程

void MonitorExit(object obj)
{
    // 验证当前线程确实持有锁
    if (obj.SyncBlock.OwnerThread != CurrentThread)
        throw new SynchronizationLockException();
    
    // 减少递归计数
    obj.SyncBlock.RecursionCount--;
    
    if (obj.SyncBlock.RecursionCount == 0)
    {
        // 真正释放锁
        obj.SyncBlock.OwnerThread = null;
        
        // 如果有等待线程,唤醒一个
        if (obj.SyncBlock.WaitQueue.Count > 0)
        {
            WakeOneWaitingThread(obj);
        }
    }
}

4、Monitor 的完整使用

4.1 基础用法 - lock 语法糖

class BasicUsage
{
    private object _lockObject = new object();
    private int _sharedCounter = 0;
    
    // 方式1:使用 lock 关键字(推荐)
    void IncrementWithLock()
    {
        lock (_lockObject)  // 编译器自动生成 Monitor.Enter/Exit
        {
            _sharedCounter++;
            // 其他临界区代码
        }
    }
    
    // 方式2:显式使用 Monitor(等价于上面的 lock)
    void IncrementWithMonitor()
    {
        bool lockTaken = false;
        try
        {
            Monitor.Enter(_lockObject, ref lockTaken);
            _sharedCounter++;
        }
        finally
        {
            if (lockTaken)
            {
                Monitor.Exit(_lockObject);
            }
        }
    }
}

为什么使用 ref bool lockTaken?

// 防止异步异常导致锁无法释放
void DangerousMethod()
{
    Monitor.Enter(_lockObject); // 不推荐!如果这里发生异常,锁不会被释放
    try
    {
        // 临界区
    }
    finally
    {
        Monitor.Exit(_lockObject);
    }
}

void SafeMethod()
{
    bool lockTaken = false;
    try
    {
        Monitor.Enter(_lockObject, ref lockTaken); // 原子操作,要么完全成功要么完全失败
        // 临界区
    }
    finally
    {
        if (lockTaken) // 只有成功获取锁才释放
        {
            Monitor.Exit(_lockObject);
        }
    }
}

4.2 高级功能 - Wait/Pulse 机制

这是 Monitor 最强大的特性,用于复杂的线程协调:

class ProducerConsumerWithMonitor
{
    private object _lockObject = new object();
    private Queue<string> _queue = new Queue<string>();
    private bool _isStopped = false;
    
    // 生产者
    public void Produce(string item)
    {
        lock (_lockObject)
        {
            // 如果队列太大,等待消费者处理
            while (_queue.Count >= 5)
            {
                Console.WriteLine("生产者: 队列已满,等待...");
                Monitor.Wait(_lockObject); // 释放锁并等待
            }
            
            _queue.Enqueue(item);
            Console.WriteLine($"生产者: 添加 {item},队列大小: {_queue.Count}");
            
            // 通知等待的消费者
            Monitor.Pulse(_lockObject);
        }
    }
    
    // 消费者
    public string Consume()
    {
        lock (_lockObject)
        {
            // 如果队列为空,等待生产者
            while (_queue.Count == 0 && !_isStopped)
            {
                Console.WriteLine("消费者: 队列为空,等待...");
                Monitor.Wait(_lockObject);
            }
            
            if (_queue.Count == 0) return null; // 已停止且队列为空
            
            string item = _queue.Dequeue();
            Console.WriteLine($"消费者: 取出 {item},队列大小: {_queue.Count}");
            
            // 通知等待的生产者
            Monitor.Pulse(_lockObject);
            
            return item;
        }
    }
    
    public void Stop()
    {
        lock (_lockObject)
        {
            _isStopped = true;
            Monitor.PulseAll(_lockObject); // 唤醒所有等待线程
        }
    }
}

// 使用示例
class Program
{
    static void Main()
    {
        var pc = new ProducerConsumerWithMonitor();
        
        // 启动生产者线程
        Thread producer = new Thread(() => {
            for (int i = 0; i < 10; i++)
            {
                pc.Produce($"产品-{i}");
                Thread.Sleep(100);
            }
        });
        
        // 启动消费者线程
        Thread consumer = new Thread(() => {
            for (int i = 0; i < 10; i++)
            {
                pc.Consume();
                Thread.Sleep(150);
            }
        });
        
        producer.Start();
        consumer.Start();
        
        producer.Join();
        consumer.Join();
    }
}

4.3 超时控制 - TryEnter

class TimeoutExample
{
    private object _lockObject = new object();
    
    public bool TryDoWork(int timeoutMs)
    {
        bool lockTaken = false;
        
        try
        {
            // 尝试在指定时间内获取锁
            lockTaken = Monitor.TryEnter(_lockObject, timeoutMs);
            
            if (lockTaken)
            {
                // 成功获取锁,执行工作
                DoCriticalWork();
                return true;
            }
            else
            {
                // 超时,执行备用方案
                Console.WriteLine($"获取锁超时 ({timeoutMs}ms),执行备用操作");
                DoFallbackWork();
                return false;
            }
        }
        finally
        {
            if (lockTaken)
            {
                Monitor.Exit(_lockObject);
            }
        }
    }
    
    private void DoCriticalWork()
    {
        Thread.Sleep(2000); // 模拟耗时工作
        Console.WriteLine("关键工作完成");
    }
    
    private void DoFallbackWork()
    {
        Console.WriteLine("执行备用工作");
    }
}

5、Wait/Pulse 的详细机制

5.1 等待队列的工作原理

class WaitPulseMechanism
{
    private object _lockObject = new object();
    
    void DemonstrateWaitPulse()
    {
        lock (_lockObject)
        {
            // 当调用 Monitor.Wait(_lockObject) 时:
            // 1. 当前线程释放 _lockObject 的锁
            // 2. 线程进入 _lockObject 的等待队列
            // 3. 线程状态改为 WaitSleepJoin
            
            // 当其他线程调用 Monitor.Pulse(_lockObject) 时:
            // 1. 从等待队列中移出一个线程
            // 2. 该线程进入就绪队列,准备重新获取锁
            
            // 当其他线程调用 Monitor.PulseAll(_lockObject) 时:
            // 1. 所有等待线程都移到就绪队列
        }
    }
}

5.2 复杂的协调示例

class ComplexCoordination
{
    private object _lockObject = new object();
    private int _currentPhase = 0;
    private int _threadsReady = 0;
    private const int TOTAL_THREADS = 3;
    
    public void WorkerThread(int threadId)
    {
        for (int phase = 0; phase < 3; phase++)
        {
            lock (_lockObject)
            {
                // 等待进入下一阶段
                while (_currentPhase != phase)
                {
                    Monitor.Wait(_lockObject);
                }
                
                _threadsReady++;
                Console.WriteLine($"线程 {threadId} 准备就绪阶段 {phase}");
                
                // 如果所有线程都就绪,推进到下一阶段
                if (_threadsReady == TOTAL_THREADS)
                {
                    _currentPhase++;
                    _threadsReady = 0;
                    Console.WriteLine($"=== 所有线程完成阶段 {phase},进入阶段 {_currentPhase} ===");
                    Monitor.PulseAll(_lockObject); // 唤醒所有线程
                }
                else
                {
                    // 等待其他线程
                    while (_currentPhase == phase)
                    {
                        Monitor.Wait(_lockObject);
                    }
                }
            }
            
            // 执行阶段工作
            Console.WriteLine($"线程 {threadId} 执行阶段 {phase} 的工作");
            Thread.Sleep(500);
        }
    }
}

6、Monitor 的底层优化

6.1 瘦锁(Thin Lock)

对于轻度竞争的情况,CLR 使用优化:

// 概念性优化
class ThinLock
{
    // 对于轻度使用,直接在对象头中存储锁信息,避免创建完整的SyncBlock
    // 使用位域存储:线程ID + 递归计数
    // 当竞争激烈时,才升级为完整的SyncBlock
}

6.2 自旋等待

在进入内核等待前,会先自旋一段时间:

bool TryEnterWithSpin(object obj, int timeoutMs)
{
    int spinCount = CalculateOptimalSpinCount();
    
    for (int i = 0; i < spinCount; i++)
    {
        if (TryEnterFastPath(obj)) return true;
        Thread.SpinWait(100); // 用户态自旋,避免内核切换
    }
    
    return EnterSlowPath(obj, timeoutMs);
}

7、最佳实践和陷阱

7.1 正确做法

class BestPractices
{
    private readonly object _lockObject = new object(); // readonly 防止意外修改
    
    public void GoodPractice1()
    {
        lock (_lockObject)
        {
            // 临界区代码尽量简短
            DoQuickOperation();
        }
        // 长时间操作放在锁外
        DoLongRunningOperation();
    }
    
    public void GoodPractice2()
    {
        // 使用明确的超时控制
        if (Monitor.TryEnter(_lockObject, 1000))
        {
            try
            {
                // 工作
            }
            finally
            {
                Monitor.Exit(_lockObject);
            }
        }
    }
}

7.2 常见陷阱

class CommonMistakes
{
    private object _lockObject = new object();
    
    // 错误1:锁住错误的对象
    public void Mistake1()
    {
        lock (this) // 不好!外部代码可能也锁住这个实例
        {
            // ...
        }
    }
    
    // 错误2:在锁内调用未知方法
    public void Mistake2()
    {
        lock (_lockObject)
        {
            SomeExternalMethod(); // 危险!可能造成死锁
        }
    }
    
    // 错误3:忘记释放锁
    public void Mistake3()
    {
        Monitor.Enter(_lockObject); // 如果没有对应的Exit,会导致死锁
        // 应该使用 try-finally 或 lock 关键字
    }
    
    // 错误4:错误的 Wait/Pulse 使用
    public void Mistake4()
    {
        // 必须在锁内调用 Wait/Pulse
        Monitor.Wait(_lockObject); // 错误!不在锁内
    }
}

8、总结

Monitor 的本质:

  • 是基于对象头同步块的软件锁

  • 提供递归获取能力(同一线程可多次获取)

  • 内置等待/通知机制(Wait/Pulse)

  • 在用户态和内核态间智能切换

核心优势:

  1. 与对象绑定:任何对象都可作为锁

  2. 递归安全:同一线程不会死锁自己

  3. 条件变量:内置复杂的线程协调能力

  4. 性能优化:瘦锁、自旋等待等优化

使用要点:

优先使用 lock 关键字

  • 复杂协调使用 Wait/Pulse

  • 总是用 try-finally 确保锁释放

  • 避免在锁内执行耗时操作

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值