编辑
2025-10-11
C#
00

目录

Monitor 的特点
使用 Monitor 的基本语法
示例:使用 Monitor 实现线程安全的计数器
代码解析
示例:使用 Monitor 的条件变量
代码解析
示例:使用 Monitor.PulseAll 进行唤醒所有等待线程
代码解析
使用 Monitor 的注意事项
结论

在C#的多线程编程中,Monitor 是一种用于同步多个线程访问共享资源的机制。它是基于对象的锁定机制,能够有效地控制对代码块的访问,防止数据的不一致,其实与lock基本一样的。本文将详细介绍 Monitor 的特点、用法,并提供多个示例以展示其应用。

Monitor 的特点

  • 独占性访问Monitor 通过锁定对象,确保同一时刻只有一个线程可以访问被锁定的代码块。
  • 高效性:相比于 MutexMonitor 的性能开销较小,适合在同一进程中的多线程环境中使用。
  • 支持条件变量Monitor 允许线程在等待某个条件时释放锁,这样其他线程可以获得锁,避免资源的浪费。
  • 易于使用Monitor 提供了较为简单的 APIs,如 EnterExitWaitPulsePulseAll

使用 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); // 确保释放锁 } } } } }

image.png

代码解析

  1. 共享资源counter 是一个静态变量,由多个线程共享。
  2. 锁对象lockObject 是一个用于同步的对象,所有线程通过该对象进行控制。
  3. 线程创建和启动:创建5个线程并启动它们,每个线程执行 IncrementCounter 方法。
  4. 计数器增加:在 Monitor.Enter(lockObject) 后,只有获得锁的线程才能执行 counter++
  5. 锁的释放:在 finally 块内调用 Monitor.Exit(lockObject),确保锁总是释放,即使发生异常。
  6. 等待线程完成:主线程使用 Join 方法确保所有工作线程完成后再输出 counter 的值。

示例:使用 Monitor 的条件变量

使用 Monitor 的条件变量,允许线程在特定条件下等待以释放锁,以下示例展示了如何使用 Monitor.WaitMonitor.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 已发送通知。"); } } }

image.png

代码解析

  1. 条件变量isReady 是一个布尔变量,用于控制 Worker 线程的执行。
  2. 工作线程Worker 线程在获得锁后,检查 isReady 的值,如果为 false,则调用 Monitor.Wait 方法,释放锁并等待通知。
  3. 通知线程Notifier 线程在处理完毕后,通过 Monitor.Pulse 发送通知,唤醒等待的 Worker 线程。
  4. 工作执行:一旦 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); } } }

image.png

代码解析

  1. 准备计数readyCount 用于计算已准备好的线程数量,而 neededCount 定义了需要满的线程数量。
  2. 工作线程:每个 Worker 在线程启动时增加 readyCount 计数,如果未达到需要数量的线程,则调用 Monitor.Wait 进入等待状态。
  3. 通知方法:当 NotifyThreads 被调用时,会使用 Monitor.PulseAll 唤醒所有等待的线程,使它们能够继续执行。

使用 Monitor 的注意事项

  • 确保释放锁:始终使用 try...finally 構造以确保在异常或早期返回时仍然释放锁。
  • 避免死锁:在使用 Monitor 时,确保不以不当顺序获取多个锁,避免死锁现象。
  • 高并发下的性能:虽然 Monitor 的性能相对较好,但在高并发情况下,可能仍会造成性能瓶颈。
  • 条件变量的使用:使用 Monitor.WaitMonitor.Pulse 时,确保它们在相同的锁定对象上调用,以确保正确执行。

结论

Monitor 是C#中一种强大的多线程同步机制,能够有效管理线程对共享资源的访问。通过上述示例,您可以看到如何利用 Monitor 来实现线程安全的操作,并控制线程的执行顺序。合理地使用 Monitor 可以显著提高多线程应用的可靠性和效率。在应用中,开发者应关注性能、错误处理及可能的竞争条件,以确保安全和高效性。

本文作者:技术老小子

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!