1.4 Monitor 监控器

Monitor监控器详解

1.Monitor简介

2.Monitor常用API

3.生产者和消费者


1.Monitor简介

Monitor是.Net中提供线程同步的机制, 它使用对象锁来实现互斥访问, 并且提供了等待和通知的机制(Wait和Pulse/PulseAll)

1).锁对象(Sync Block)

a..Net中, 每个对象都有一个同步块索引(Sync Block Index), 该索引指向一个同步块(Sync Block); 同步块中包含了用

于同步的信息, 如持有锁的线程, 等待队列等

b.当我们使用Monitor.Enter(obj), CLR会检查该对象的同步块索引, 如果没有同步块, 则会分配一个; 然后, 尝试获取该

同步块上的锁

2).就绪队列和等待队列

a.每个锁对象都有两个队列: 就绪队列和等待队列

b.就绪队列中包含那些已经准备好获取锁但还在等待锁释放的线程

c.等待队列中包含那些调用了Wait方法而在等待通知的线程

3).互斥锁

a.Monitor使用互斥锁来确保同一时间只有一个线程可以进入临界区

b.当一个线程调用Monitor.Enter(obj), 如果没有锁被其他线程占用, 则该线程获得锁, 并且进入临界区

c.如果锁已经被其他线程占用, 那么当前线程会进入等待状态, 直到锁被释放

4).等待和通知(Wait和Pulse)

a.Monitor.Wait(obj)会释放锁, 并将当前线程放入锁的等待队列中, 然后进入等待状态

b.当其他线程调用Monitor.Pulse(obj), 会从等待队列中取出一个线程将其移到就绪队列中, 这样该线程就可以在锁被释

放后重新获取并继续执行

c.Monitor.PulseAll(obj)则会移动等待队列中的所有线程到就绪队列
5).实现细节

a.同步块实际是一个结构, 它包含了以下信息

- 所有者线程(当前持有锁的线程)

- 递归计数(同一个线程可以多次获取锁, 即重入)

- 就绪队列和等待队列的引用

b.当线程调用Monitor.Enter时, 会检查当前线程是否已经是锁的持有者, 如果是, 则递归计数+1

c.当线程调用Monitor.Exit时, 递归计数减1, 当递归计数为零时, 锁被释放, 然后会从就绪队列中唤醒一个线程

6).性能优化

a.Monitor被实现为一个混合锁, 它首先会尝试自旋(spin)一段时间, 如果自旋后还获取不到锁, 才会将线程切换到等待状态

b.自旋等待对于锁持有时间很短的场景非常有效, 可以避免上下文切换的开销

2.Monitor常用API

a.Monitor.Enter 获取锁

b.Monitor.Exit 释放锁

在这里插入图片描述

c.Monitor.Wait

- 作用: 释放指定对象上的锁, 然后"阻塞当前线程", 直到它重新获取该锁; 通常, 线程在等待状态时会被其他线程通过 

Pulse或PulseAll信号唤醒; 但注意, 唤醒后它需要重新竞争锁, 直到成功获取锁后Wait方法才会返回

- 使用Wait时, 线程会暂时放弃锁, 并进入等待队列, 直到被Pulse唤醒; 然后它从等待队列移到就绪队列, 等到锁可用时重

新获取锁, 然后Wait返回

d.Pulse

唤醒一个在lockObject上等待的线程(如果有), 被唤醒的线程会尝试重新获取锁, 一旦获取成功, 它就会继续执行

e.PulseAll

唤醒所有在lockObject上等待的线程, 如果线程被PulseAll唤醒(移到了就绪队列), 然后竞争锁失败, 它会继续留在就绪队

列中, 直到锁被释放时再次竞争; 这个过程可能会重复, 直到它成功获取锁

f.TryEnter 

尝试进入临界区, 可以设置超时时间(毫秒): "Wait、Pulse和PulseAll必须在已获取锁的临界区内调用"

3.生产者和消费者

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

class ProducerConsumerWithMonitor
{
    private Queue<int> queue = new Queue<int>();
    private object lockObject = new object();
    private const int MaxSize = 5;

    // 生产者方法
    public void Produce(int item)
    {
        Monitor.Enter(lockObject);
        try
        {
            // 如果队列已满,等待消费者消费
            while (queue.Count >= MaxSize)
            {
                Console.WriteLine($"生产者 {Thread.CurrentThread.ManagedThreadId} 等待,队列已满");
                Monitor.Wait(lockObject);
            }
            
            queue.Enqueue(item);
            Console.WriteLine($"生产者 {Thread.CurrentThread.ManagedThreadId} 生产: {item}, 队列大小: {queue.Count}");
            
            // 通知消费者有新产品了
            Monitor.Pulse(lockObject);
        }
        finally
        {
            Monitor.Exit(lockObject);
        }
    }

    // 消费者方法
    public void Consume()
    {
        Monitor.Enter(lockObject);
        try
        {
            // 如果队列为空,等待生产者生产
            while (queue.Count == 0)
            {
                Console.WriteLine($"消费者 {Thread.CurrentThread.ManagedThreadId} 等待,队列为空");
                Monitor.Wait(lockObject);
            }
            
            int item = queue.Dequeue();
            Console.WriteLine($"消费者 {Thread.CurrentThread.ManagedThreadId} 消费: {item}, 队列大小: {queue.Count}");
            
            // 通知生产者有空位了
            Monitor.Pulse(lockObject);
        }
        finally
        {
            Monitor.Exit(lockObject);
        }
    }
}
// 使用示例
class Program
{
    static void Main()
    {
        var pc = new ProducerConsumerWithMonitor();
        
        // 启动生产者线程
        Thread producer = new Thread(() =>
        {
            for (int i = 1; i <= 10; i++)
            {
                pc.Produce(i);
                Thread.Sleep(100);
            }
        });
        
        // 启动消费者线程
        Thread consumer = new Thread(() =>
        {
            for (int i = 1; i <= 10; i++)
            {
                pc.Consume();
                Thread.Sleep(150);
            }
        });
        
        producer.Start();
        consumer.Start();
        
        producer.Join();
        consumer.Join();
        
        Console.WriteLine("完成!");
    }
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值