在C#的多线程编程中,Monitor
是一种用于同步多个线程访问共享资源的机制。它是基于对象的锁定机制,能够有效地控制对代码块的访问,防止数据的不一致,其实与lock基本一样的。本文将详细介绍 Monitor
的特点、用法,并提供多个示例以展示其应用。
Monitor
的特点Monitor
通过锁定对象,确保同一时刻只有一个线程可以访问被锁定的代码块。Mutex
,Monitor
的性能开销较小,适合在同一进程中的多线程环境中使用。Monitor
允许线程在等待某个条件时释放锁,这样其他线程可以获得锁,避免资源的浪费。Monitor
提供了较为简单的 APIs,如 Enter
、Exit
、Wait
、Pulse
和 PulseAll
。Monitor
的基本语法以下是 Monitor
的基本用法示例:
C#object lockObject = new object();
Monitor.Enter(lockObject); // 请求锁
try {
// ... 访问共享资源
} finally {
Monitor.Exit(lockObject); // 释放锁
}
Monitor
实现线程安全的计数器以下示例展示了如何使用 Monitor
来实现一个线程安全的计数器,确保只有一个线程可以对计数器进行更新。
C#namespace AppMonitor01
{
internal class Program
{
private static int counter = 0; // 共享资源
private static object lockObject = new object(); // 用于锁定代码块
static void Main(string[] args)
{
Thread[] threads = new Thread[5];
for (int i = 0; i < threads.Length; i++)
{
threads[i] = new Thread(IncrementCounter);
threads[i].Start();
}
foreach (var thread in threads)
{
thread.Join();
}
Console.WriteLine($"最终计数器的值: {counter}");
}
static void IncrementCounter()
{
for (int i = 0; i < 1000; i++)
{
Monitor.Enter(lockObject); // 请求锁
try
{
counter++; // 增加计数
}
finally
{
Monitor.Exit(lockObject); // 确保释放锁
}
}
}
}
}
counter
是一个静态变量,由多个线程共享。lockObject
是一个用于同步的对象,所有线程通过该对象进行控制。IncrementCounter
方法。Monitor.Enter(lockObject)
后,只有获得锁的线程才能执行 counter++
。finally
块内调用 Monitor.Exit(lockObject)
,确保锁总是释放,即使发生异常。Join
方法确保所有工作线程完成后再输出 counter
的值。Monitor
的条件变量使用 Monitor
的条件变量,允许线程在特定条件下等待以释放锁,以下示例展示了如何使用 Monitor.Wait
和 Monitor.Pulse
。
C#using System;
using System.Threading;
class Program {
private static object lockObject = new object(); // 用于同步的对象
private static bool isReady = false; // 条件变量
static void Main(string[] args) {
Thread workerThread = new Thread(Worker);
Thread notifierThread = new Thread(Notifier);
workerThread.Start();
notifierThread.Start();
workerThread.Join();
notifierThread.Join();
}
static void Worker() {
Console.WriteLine("Worker 正在等待通知...");
lock (lockObject) {
while (!isReady) {
Monitor.Wait(lockObject); // 等待通知
}
Console.WriteLine("Worker 收到通知,开始工作。");
}
}
static void Notifier() {
Console.WriteLine("Notifier 正在处理...");
Thread.Sleep(2000); // 模拟处理时间
lock (lockObject) {
isReady = true;
Monitor.Pulse(lockObject); // 发送通知
Console.WriteLine("Notifier 已发送通知。");
}
}
}
isReady
是一个布尔变量,用于控制 Worker
线程的执行。Worker
线程在获得锁后,检查 isReady
的值,如果为 false
,则调用 Monitor.Wait
方法,释放锁并等待通知。Notifier
线程在处理完毕后,通过 Monitor.Pulse
发送通知,唤醒等待的 Worker
线程。Worker
收到通知,就会继续执行相应的工作。Monitor.PulseAll
进行唤醒所有等待线程在某些情况下,可能需要唤醒所有等待的线程,可以使用 Monitor.PulseAll
方法。以下示例展示了如何使用 Monitor.PulseAll
。
C#using System;
using System.Threading;
class Program {
private static object lockObject = new object();
private static int readyCount = 0; // 准备好的线程计数
private static int neededCount = 3; // 需要满多少个线程
static void Main(string[] args) {
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(Worker);
thread.Start(i + 1);
}
Thread.Sleep(2000); // 等待一些时间让所有线程开始
NotifyThreads(); // 调用通知方法
}
static void Worker(object index) {
Console.WriteLine($"线程 {index} 正在准备...");
lock (lockObject) {
readyCount++;
if (readyCount < neededCount) {
Monitor.Wait(lockObject); // 等待通知
}
Console.WriteLine($"线程 {index} 继续执行。");
}
}
static void NotifyThreads() {
Console.WriteLine("准备好所有线程。");
lock (lockObject) {
// 使得所有等待的线程都能继续
Monitor.PulseAll(lockObject);
}
}
}
readyCount
用于计算已准备好的线程数量,而 neededCount
定义了需要满的线程数量。Worker
在线程启动时增加 readyCount
计数,如果未达到需要数量的线程,则调用 Monitor.Wait
进入等待状态。NotifyThreads
被调用时,会使用 Monitor.PulseAll
唤醒所有等待的线程,使它们能够继续执行。Monitor
的注意事项try...finally
構造以确保在异常或早期返回时仍然释放锁。Monitor
时,确保不以不当顺序获取多个锁,避免死锁现象。Monitor
的性能相对较好,但在高并发情况下,可能仍会造成性能瓶颈。Monitor.Wait
和 Monitor.Pulse
时,确保它们在相同的锁定对象上调用,以确保正确执行。Monitor
是C#中一种强大的多线程同步机制,能够有效管理线程对共享资源的访问。通过上述示例,您可以看到如何利用 Monitor
来实现线程安全的操作,并控制线程的执行顺序。合理地使用 Monitor
可以显著提高多线程应用的可靠性和效率。在应用中,开发者应关注性能、错误处理及可能的竞争条件,以确保安全和高效性。
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!