Monitor和Lock的区别

1.Lock是Monitor的语法糖。

2.Lock只能针对引用类型加锁。

3.Monitor能够对值类型进行加锁,实质上是Monitor.Enter(object)对值类型装箱。

4.Monitor还有其他的一些功能。

示例代码:

class Program
    {
        private static object obj = new object();
        public void LockSomething()
        {
            lock (obj)
            {
                dosomething();
            }
        }
        public void MonitorSomeThing()
        {
            try
            {
                Monitor.Enter(obj);
                dosomething();
            }
            catch(Exception ex)
            {

            }
            finally
            {
                Monitor.Exit(obj);
            }
        }

        public void dosomething()
        { 
            //做具体的事情
        }
    }
<think>我们之前已经讨论过`lock`关键字,它实际上是基于`Monitor`的语法糖。现在我们来探讨`Monitor``Lock`的使用场景,重点在于它们各自适用的场景以及如何选择。 ### 1. 基本概念 - **lock关键字**:C#提供的一种简洁的线程同步机制,用于确保同一时刻只有一个线程可以访问特定代码块(临界区)。它自动处理`Monitor.Enter``Monitor.Exit`(包括异常情况下的释放)。 - **Monitor类**:提供了更细粒度的控制,例如等待(Wait)、通知(Pulse/PulseAll)以及尝试获取锁(TryEnter)等。`Monitor`是`lock`的底层实现。 ### 2. 使用场景对比 #### 场景1:基本互斥锁 - **lock关键字**:当只需要简单的互斥锁时,使用`lock`是最简洁的选择。 ```csharp private readonly object _lockObj = new object(); public void SafeIncrement() { lock (_lockObj) { // 临界区代码 counter++; } } ``` - **Monitor类**:在相同场景下也可以使用,但代码更冗长。 ```csharp private readonly object _lockObj = new object(); public void SafeIncrement() { bool lockTaken = false; try { Monitor.Enter(_lockObj, ref lockTaken); // 临界区代码 counter++; } finally { if (lockTaken) Monitor.Exit(_lockObj); } } ``` **选择建议**:优先使用`lock`,因为它更简洁且不易出错。 #### 场景2:带超时的锁获取 当需要尝试获取锁一段时间,如果超时则执行其他操作时,`Monitor.TryEnter`提供了这个能力,而`lock`关键字本身不支持超时。 ```csharp public void TryToUpdate() { bool lockTaken = false; try { // 尝试在500毫秒内获取锁 lockTaken = Monitor.TryEnter(_lockObj, 500); if (lockTaken) { // 成功获取锁,执行操作 UpdateData(); } else { // 超时处理 LogTimeout(); } } finally { if (lockTaken) Monitor.Exit(_lockObj); } } ``` **选择建议**:需要超时机制时,必须使用`Monitor.TryEnter`。 #### 场景3:条件等待与通知(生产者-消费者模式) `Monitor`提供了`Wait`、`Pulse``PulseAll`方法,用于实现更复杂的线程交互,例如生产者-消费者队列。 ```csharp class BlockingQueue<T> { private readonly Queue<T> _queue = new Queue<T>(); private readonly object _lockObj = new object(); public void Enqueue(T item) { lock (_lockObj) { _queue.Enqueue(item); // 通知一个等待的线程(消费者) Monitor.Pulse(_lockObj); } } public T Dequeue() { lock (_lockObj) { while (_queue.Count == 0) { // 等待直到有数据 Monitor.Wait(_lockObj); } return _queue.Dequeue(); } } } ``` **选择建议**:需要线程间协调(等待/通知)时,必须使用`Monitor`的方法,因为`lock`关键字本身不提供这些功能。 ### 3. 使用注意事项 - **锁对象选择**:必须是引用类型(非值类型),并且通常是私有的、只读的对象。 - **避免死锁**:注意锁的顺序,并尽量使用超时机制。 - **异常处理**:确保在异常情况下锁能被正确释放(使用`finally`块)。 ### 4. 性能考虑 - 尽量减少锁的持有时间,避免在锁内执行耗时操作(如I/O操作)。 - 在竞争不激烈的情况下,`lock`的性能已经足够好;在高度竞争的场景中,可以考虑其他同步机制(如`SemaphoreSlim`、`ReaderWriterLockSlim`等)。 ### 结论 - 简单互斥场景:使用`lock`关键字。 - 需要超时或尝试获取锁:使用`Monitor.TryEnter`。 - 线程间协调(等待/通知):使用`Monitor.Wait`/`Pulse`。 --- ### §§相关问题§§: 1. 在C#中,`lock`关键字`Monitor.Enter`有什么区别? 2. 为什么在`Monitor.Wait`调用前必须先获取锁? 3. 如何避免在使用`Monitor.Pulse`时出现信号丢失? 4. 在多线程编程中,除了`lock``Monitor`,还有哪些常用的同步机制? </think>### C# `Monitor` vs `Lock` 使用场景详解 #### 一、核心区别总结 | **特性** | `lock` 关键字 | `Monitor` 类 | |------------------|-----------------------------------|----------------------------------------| | **语法形式** | 语法糖(编译器转换) | 显式 API 调用 | | **功能范围** | 基础互斥锁 | 高级同步(超时、通知等) | | **异常处理** | 自动释放(finally 块) | 需手动确保 Exit 调用 | | **超时控制** | 不支持 | 支持 TryEnter 超时 | | **等待/通知** | 不支持 | 支持 Wait/Pulse/PulseAll | | **使用复杂度** | 简单直观 | 更灵活但需要更多样板代码 | #### 二、`lock` 关键字的适用场景 1. **基础临界区保护(首选)** ```csharp private readonly object _lockObj = new object(); private int _counter; public void Increment() { lock (_lockObj) // 简洁的临界区保护 { _counter++; } } ``` **适用原因**:语法简洁,自动处理异常释放,适合大多数同步需求 2. **实例成员保护** ```csharp public class BankAccount { private decimal _balance; private readonly object _balanceLock = new object(); public void Deposit(decimal amount) { lock (_balanceLock) // 保护实例状态 { _balance += amount; } } } ``` 3. **短期操作的同步** **适用原因**:`lock` 内部采用快速路径优化,执行时间短时性能更佳 #### 三、`Monitor` 类的适用场景 1. **带超时的锁获取** ```csharp public bool TryUpdateData(int timeoutMs) { bool lockTaken = false; try { Monitor.TryEnter(_lockObj, timeoutMs, ref lockTaken); if (!lockTaken) return false; // 超时处理 // 更新数据 return true; } finally { if (lockTaken) Monitor.Exit(_lockObj); } } ``` **适用原因**:防止死锁,适应高竞争环境 2. **生产者-消费者模式** ```csharp public class MessageQueue { private readonly Queue<string> _queue = new(); private readonly object _syncRoot = new(); public void Enqueue(string message) { lock (_syncRoot) { _queue.Enqueue(message); Monitor.Pulse(_syncRoot); // 通知等待线程 } } public string Dequeue() { lock (_syncRoot) { while (_queue.Count == 0) Monitor.Wait(_syncRoot); // 释放锁并等待 return _queue.Dequeue(); } } } ``` **适用原因**:必须使用 `Wait/Pulse` 实现线程协调 3. **细粒度资源管理** ```csharp private void ProcessWithLockHierarchy() { var lockTaken1 = false; var lockTaken2 = false; try { Monitor.TryEnter(_lock1, 100, ref lockTaken1); if (!lockTaken1) throw new TimeoutException(); Monitor.Enter(_lock2, ref lockTaken2); // 获取第二把锁 // ... 关键操作 ... } finally { if (lockTaken2) Monitor.Exit(_lock2); if (lockTaken1) Monitor.Exit(_lock1); // 按相反顺序释放 } } ``` **适用原因**:需要精确控制多个锁的获取/释放顺序 #### 四、选择指南 1. **优先选择 `lock` 当**: - 需要简单互斥访问 - 操作执行时间短(< 1ms) - 不需要超时控制 - 不需要线程间通知 2. **必须使用 `Monitor` 当**: - 需要设置锁获取超时 - 实现 Wait/Pulse 线程协调 - 处理复杂锁层次结构 - 需要在锁内调用其他可能阻塞的方法 #### 五、错误场景示例 ```csharp // 错误:在lock块内调用Monitor.Wait lock (syncObj) { Monitor.Wait(syncObj); // 导致IllegalMonitorStateException } // 正确:使用Monitor的完整控制 Monitor.Enter(syncObj); try { Monitor.Wait(syncObj); // 正确使用 } finally { Monitor.Exit(syncObj); } ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值