07、使用监视器为锁添加超时功能

使用监视器为锁添加超时功能

知识点

什么是Monitor

Monitor类是.NET Framework中提供的同步原语,它提供了比lock语句更灵活的锁定机制。Monitor允许设置超时时间,避免线程无限期等待锁。

Monitor的优势

  1. 超时控制:可以设置获取锁的超时时间
  2. 可中断:支持线程中断
  3. 灵活性:提供更细粒度的控制
  4. 死锁预防:通过超时机制避免死锁

Monitor的核心方法

  • Monitor.Enter(obj):获取锁
  • Monitor.TryEnter(obj, timeout):尝试在指定时间内获取锁
  • Monitor.Exit(obj):释放锁
  • Monitor.Wait(obj):等待条件变量
  • Monitor.Pulse(obj):通知等待的线程
  • Monitor.PulseAll(obj):通知所有等待的线程

超时机制的重要性

  • 避免死锁
  • 提高系统响应性
  • 实现优雅的错误处理
  • 支持取消操作

代码案例

案例1:基本的Monitor超时使用

using System;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    private static readonly object lockObject = new object();
    private static int sharedResource = 0;
    
    static void Main(string[] args)
    {
        Console.WriteLine("Monitor超时示例");
        
        // 启动一个长时间持有锁的任务
        Task longTask = Task.Run(() => LongRunningTask());
        Thread.Sleep(100); // 确保长任务先获得锁
        
        // 启动一个尝试获取锁的任务
        Task shortTask = Task.Run(() => TryAcquireLockWithTimeout());
        
        Task.WaitAll(longTask, shortTask);
        Console.WriteLine("所有任务完成");
    }
    
    static void LongRunningTask()
    {
        Console.WriteLine("长任务:尝试获取锁...");
        
        lock (lockObject)
        {
            Console.WriteLine("长任务:获得锁,开始长时间工作...");
            Thread.Sleep(5000); // 模拟长时间工作
            sharedResource = 100;
            Console.WriteLine("长任务:工作完成,释放锁");
        }
    }
    
    static void TryAcquireLockWithTimeout()
    {
        Console.WriteLine("短任务:尝试获取锁(3秒超时)...");
        
        bool lockAcquired = false;
        try
        {
            Monitor.TryEnter(lockObject, TimeSpan.FromSeconds(3), ref lockAcquired);
            
            if (lockAcquired)
            {
                Console.WriteLine("短任务:成功获得锁");
                // 执行一些工作
                sharedResource += 10;
                Console.WriteLine($"短任务:共享资源值 = {sharedResource}");
            }
            else
            {
                Console.WriteLine("短任务:获取锁超时,继续其他工作");
                // 执行替代逻辑
                Console.WriteLine("短任务:执行不需要锁的操作");
            }
        }
        finally
        {
            if (lockAcquired)
            {
                Monitor.Exit(lockObject);
                Console.WriteLine("短任务:释放锁");
            }
        }
    }
}

案例2:生产者消费者模式with Monitor

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

class ProducerConsumerWithMonitor
{
    private static readonly object lockObject = new object();
    private static Queue<int> buffer = new Queue<int>();
    private static bool isProducing = true;
    private const int MaxBufferSize = 5;
    
    static void Main(string[] args)
    {
        Console.WriteLine("生产者消费者模式(Monitor实现)");
        
        // 启动生产者
        Task producer = Task.Run(() => Producer());
        
        // 启动多个消费者
        Task[] consumers = new Task[3];
        for (int i = 0; i < consumers.Length; i++)
        {
            int consumerId = i;
            consumers[i] = Task.Run(() => Consumer(consumerId));
        }
        
        // 运行5秒后停止生产
        Thread.Sleep(5000);
        isProducing = false;
        
        // 通知所有等待的消费者
        lock (lockObject)
        {
            Monitor.PulseAll(lockObject);
        }
        
        Task.WaitAll(new[] { producer }.Concat(consumers).ToArray());
        Console.WriteLine("程序结束");
    }
    
    static void Producer()
    {
        int item = 1;
        
        while (isProducing)
        {
            bool lockAcquired = false;
            try
            {
                Monitor.TryEnter(lockObject, TimeSpan.FromSeconds(1), ref lockAcquired);
                
                if (lockAcquired)
                {
                    // 等待缓冲区有空间
                    while (buffer.Count >= MaxBufferSize && isProducing)
                    {
                        Console.WriteLine("生产者:缓冲区满,等待...");
                        Monitor.Wait(lockObject, TimeSpan.FromSeconds(1));
                    }
                    
                    if (isProducing && buffer.Count < MaxBufferSize)
                    {
                        buffer.Enqueue(item);
                        Console.WriteLine($"生产者:生产了 {item},缓冲区大小:{buffer.Count}");
                        item++;
                        
                        // 通知消费者
                        Monitor.PulseAll(lockObject);
                    }
                }
                else
                {
                    Console.WriteLine("生产者:获取锁超时");
                }
            }
            finally
            {
                if (lockAcquired)
                {
                    Monitor.Exit(lockObject);
                }
            }
            
            Thread.Sleep(500); // 模拟生产时间
        }
        
        Console.WriteLine("生产者:停止生产");
    }
    
    static void Consumer(int consumerId)
    {
        while (true)
        {
            bool lockAcquired = false;
            try
            {
                Monitor.TryEnter(lockObject, TimeSpan.FromSeconds(2), ref lockAcquired);
                
                if (lockAcquired)
                {
                    // 等待缓冲区有数据
                    while (buffer.Count == 0 && isProducing)
                    {
                        Console.WriteLine($"消费者{consumerId}:缓冲区空,等待...");
                        Monitor.Wait(lockObject, TimeSpan.FromSeconds(1));
                    }
                    
                    if (buffer.Count > 0)
                    {
                        int item = buffer.Dequeue();
                        Console.WriteLine($"消费者{consumerId}:消费了 {item},缓冲区大小:{buffer.Count}");
                        
                        // 通知生产者
                        Monitor.PulseAll(lockObject);
                    }
                    else if (!isProducing)
                    {
                        Console.WriteLine($"消费者{consumerId}:生产结束,退出");
                        break;
                    }
                }
                else
                {
                    Console.WriteLine($"消费者{consumerId}:获取锁超时");
                    if (!isProducing) break;
                }
            }
            finally
            {
                if (lockAcquired)
                {
                    Monitor.Exit(lockObject);
                }
            }
            
            Thread.Sleep(1000); // 模拟消费时间
        }
    }
}

案例3:死锁检测和恢复

using System;
using System.Threading;
using System.Threading.Tasks;

class DeadlockDetection
{
    private static readonly object lock1 = new object();
    private static readonly object lock2 = new object();
    
    static void Main(string[] args)
    {
        Console.WriteLine("死锁检测和恢复示例");
        
        Task task1 = Task.Run(() => SafeMethod1());
        Task task2 = Task.Run(() => SafeMethod2());
        
        bool completed = Task.WaitAll(new[] { task1, task2 }, TimeSpan.FromSeconds(10));
        
        if (completed)
        {
            Console.WriteLine("所有任务成功完成");
        }
        else
        {
            Console.WriteLine("检测到可能的死锁或超时");
        }
    }
    
    static void SafeMethod1()
    {
        bool lock1Acquired = false;
        bool lock2Acquired = false;
        
        try
        {
            Console.WriteLine("方法1:尝试获取lock1...");
            Monitor.TryEnter(lock1, TimeSpan.FromSeconds(2), ref lock1Acquired);
            
            if (lock1Acquired)
            {
                Console.WriteLine("方法1:获得lock1");
                Thread.Sleep(1000); // 模拟工作
                
                Console.WriteLine("方法1:尝试获取lock2...");
                Monitor.TryEnter(lock2, TimeSpan.FromSeconds(3), ref lock2Acquired);
                
                if (lock2Acquired)
                {
                    Console.WriteLine("方法1:获得lock2,执行工作");
                    Thread.Sleep(1000);
                    Console.WriteLine("方法1:工作完成");
                }
                else
                {
                    Console.WriteLine("方法1:获取lock2超时,执行替代方案");
                    // 执行不需要lock2的替代逻辑
                }
            }
            else
            {
                Console.WriteLine("方法1:获取lock1超时");
            }
        }
        finally
        {
            if (lock2Acquired)
            {
                Monitor.Exit(lock2);
                Console.WriteLine("方法1:释放lock2");
            }
            if (lock1Acquired)
            {
                Monitor.Exit(lock1);
                Console.WriteLine("方法1:释放lock1");
            }
        }
    }
    
    static void SafeMethod2()
    {
        bool lock1Acquired = false;
        bool lock2Acquired = false;
        
        try
        {
            Console.WriteLine("方法2:尝试获取lock2...");
            Monitor.TryEnter(lock2, TimeSpan.FromSeconds(2), ref lock2Acquired);
            
            if (lock2Acquired)
            {
                Console.WriteLine("方法2:获得lock2");
                Thread.Sleep(1000); // 模拟工作
                
                Console.WriteLine("方法2:尝试获取lock1...");
                Monitor.TryEnter(lock1, TimeSpan.FromSeconds(3), ref lock1Acquired);
                
                if (lock1Acquired)
                {
                    Console.WriteLine("方法2:获得lock1,执行工作");
                    Thread.Sleep(1000);
                    Console.WriteLine("方法2:工作完成");
                }
                else
                {
                    Console.WriteLine("方法2:获取lock1超时,执行替代方案");
                    // 执行不需要lock1的替代逻辑
                }
            }
            else
            {
                Console.WriteLine("方法2:获取lock2超时");
            }
        }
        finally
        {
            if (lock1Acquired)
            {
                Monitor.Exit(lock1);
                Console.WriteLine("方法2:释放lock1");
            }
            if (lock2Acquired)
            {
                Monitor.Exit(lock2);
                Console.WriteLine("方法2:释放lock2");
            }
        }
    }
}

知识点总结

  1. Monitor的核心优势:

    • 提供超时机制,避免无限等待
    • 支持条件变量(Wait/Pulse)
    • 更灵活的锁控制
  2. 超时策略:

    • 合理设置超时时间
    • 提供替代执行路径
    • 避免过短的超时导致性能问题
  3. 最佳实践:

    • 总是使用try-finally确保锁的释放
    • 使用TryEnter而不是Enter来避免死锁
    • 合理使用Wait和Pulse进行线程协调
  4. 性能考虑:

    • Monitor比lock稍慢,但提供更多功能
    • 避免频繁的超时操作
    • 合理设计锁的粒度
  5. 错误处理:

    • 处理超时情况
    • 提供备用方案
    • 记录和监控锁争用情况
posted @ 2025-08-27 01:24  jessqiu  阅读(32)  评论(0)    收藏  举报