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("完成!");
}
}