跳到主要内容

C# Monitor 类

介绍

在多线程编程中,多个线程可能会同时访问共享资源,从而导致竞争条件(Race Condition)。为了避免这种情况,C# 提供了 Monitor 类,用于实现线程同步。Monitor 类通过锁定对象来确保同一时间只有一个线程可以访问共享资源。

Monitor 类是 C# 中最基本的线程同步机制之一,它提供了 EnterExitWaitPulsePulseAll 等方法,用于控制线程的访问和通信。

Monitor 类的基本用法

Monitor 类的核心功能是通过锁定对象来实现线程同步。以下是 Monitor 类的基本用法:

csharp
object lockObject = new object();

void ThreadSafeMethod()
{
lock (lockObject)
{
// 临界区代码
}
}

在上面的代码中,lock 语句实际上是 Monitor 类的语法糖。它等价于以下代码:

csharp
object lockObject = new object();

void ThreadSafeMethod()
{
Monitor.Enter(lockObject);
try
{
// 临界区代码
}
finally
{
Monitor.Exit(lockObject);
}
}

代码示例

以下是一个简单的示例,展示了如何使用 Monitor 类来保护共享资源:

csharp
using System;
using System.Threading;

class Program
{
private static object lockObject = new object();
private static int sharedResource = 0;

static void Main(string[] args)
{
Thread thread1 = new Thread(IncrementResource);
Thread thread2 = new Thread(IncrementResource);

thread1.Start();
thread2.Start();

thread1.Join();
thread2.Join();

Console.WriteLine("Final value of sharedResource: " + sharedResource);
}

static void IncrementResource()
{
for (int i = 0; i < 100000; i++)
{
Monitor.Enter(lockObject);
try
{
sharedResource++;
}
finally
{
Monitor.Exit(lockObject);
}
}
}
}

输出:

Final value of sharedResource: 200000

在这个示例中,两个线程同时尝试增加 sharedResource 的值。通过使用 Monitor 类,我们确保了每次只有一个线程可以访问 sharedResource,从而避免了竞争条件。

Monitor 类的高级用法

除了基本的锁定功能,Monitor 类还提供了 WaitPulsePulseAll 方法,用于线程间的通信。

Wait 方法

Wait 方法使当前线程释放锁并进入等待状态,直到其他线程调用 PulsePulseAll 方法唤醒它。

Pulse 和 PulseAll 方法

Pulse 方法唤醒一个等待的线程,而 PulseAll 方法唤醒所有等待的线程。

代码示例

以下是一个使用 WaitPulse 的示例,展示了如何实现生产者-消费者模式:

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

class Program
{
private static object lockObject = new object();
private static Queue<int> queue = new Queue<int>();

static void Main(string[] args)
{
Thread producer = new Thread(Produce);
Thread consumer = new Thread(Consume);

producer.Start();
consumer.Start();

producer.Join();
consumer.Join();
}

static void Produce()
{
for (int i = 0; i < 10; i++)
{
Monitor.Enter(lockObject);
try
{
queue.Enqueue(i);
Console.WriteLine("Produced: " + i);
Monitor.Pulse(lockObject);
}
finally
{
Monitor.Exit(lockObject);
}
Thread.Sleep(100);
}
}

static void Consume()
{
while (true)
{
Monitor.Enter(lockObject);
try
{
while (queue.Count == 0)
{
Monitor.Wait(lockObject);
}
int item = queue.Dequeue();
Console.WriteLine("Consumed: " + item);
}
finally
{
Monitor.Exit(lockObject);
}
}
}
}

输出:

Produced: 0
Consumed: 0
Produced: 1
Consumed: 1
...
Produced: 9
Consumed: 9

在这个示例中,生产者线程将数据放入队列,而消费者线程从队列中取出数据。通过使用 WaitPulse,我们确保了消费者线程在队列为空时进入等待状态,直到生产者线程添加新数据并唤醒它。

实际应用场景

Monitor 类在实际应用中有很多用途,例如:

  • 资源池管理:在多线程环境中管理有限的资源(如数据库连接池)。
  • 任务队列:实现生产者-消费者模式,处理异步任务。
  • 线程间通信:协调多个线程的执行顺序。

总结

Monitor 类是 C# 中实现线程同步的重要工具。通过使用 Monitor 类,我们可以避免多线程环境中的竞争条件,并实现线程间的通信。掌握 Monitor 类的基本用法和高级功能,对于编写高效、安全的多线程程序至关重要。

附加资源与练习

  • 练习:尝试修改生产者-消费者示例,使其支持多个生产者和消费者。
  • 资源:阅读 C# 官方文档 以了解更多关于 Monitor 类的详细信息。
提示

在多线程编程中,始终确保在 finally 块中释放锁,以避免死锁。