当前位置: 首页 > news >正文

聊透多线程编程-线程互斥与同步-12. C# Monitor类实现线程互斥

目录

一、什么是临界区?

二、Monitor类的用途

三、Monitor的基本用法

四、Monitor的工作原理

五、使用示例1-保护共享变量

解释:

六、使用示例2-线程间信号传递

解释:

七、注意事项

八、总结


 

在多线程编程中,线程之间的同步和互斥是确保程序正确运行的关键。C# 提供了多种机制来实现线程同步,其中 Monitor 类是一个底层但功能强大的工具,用于实现线程间的互斥访问。本文将详细介绍如何使用 Monitor 类实现线程互斥,并通过示例展示其工作原理。


一、什么是临界区?

在多线程编程中,临界区是指一段需要互斥访问的代码块,通常涉及对共享资源的操作。为了避免多个线程同时操作共享资源而导致数据竞争或状态不一致,我们需要对临界区代码进行保护。

例如,如果两个线程同时修改一个共享变量,可能会导致最终结果不符合预期。因此,我们需要一种机制来确保同一时间只有一个线程可以进入临界区。


二、Monitor类的简介

Monitor 类是 .NET 提供的一个低级线程同步工具,主要用于实现线程间的互斥访问(Monitor 实现的线程互斥主要是一种软件实现方法,但并不是基于经典的线程同步算法如单标志法、双标志法或 Peterson 方法来实现的,而是基于现代操作系统和硬件提供的更高级别的同步原语(如原子操作和内核对象)构建的,这是因为单标志法、双标志法和 Peterson 方法都依赖于轮询(busy-waiting),这会导致 CPU 资源浪费,不适合现代多处理器环境)。与 lock 关键字不同,Monitor 提供了更细粒度的控制能力,允许开发者手动管理锁的获取和释放。

Monitor 的主要方法包括:

  • Monitor.Enter(object):尝试获取指定对象的锁。如果锁已被占用,则当前线程会被阻塞,直到锁被释放。
  • Monitor.TryEnter(object):尝试获取指定对象的锁,但不会无限期阻塞。它返回一个布尔值,指示是否成功获取锁。可以通过重载版本指定超时时间(以毫秒为单位),如果在指定时间内未能获取锁,则返回 false。
  • Monitor.Exit(object):释放指定对象的锁。
  • Monitor.Wait(object):释放锁并使当前线程进入等待状态,直到其他线程调用 Monitor.Pulse 或 Monitor.PulseAll。
  • Monitor.Pulse(object):通知等待队列中的一个线程继续执行。
  • Monitor.PulseAll(object):通知等待队列中的所有线程继续执行。

三、Monitor的基本用法

Monitor 的基本用法类似于 lock,但需要手动调用 EnterExit 方法。以下是一个简单的例子:

private static readonly object _lock = new object(); // 锁对象Monitor.Enter(_lock);
try
{// 需要同步的代码块
}
finally
{Monitor.Exit(_lock); // 确保锁一定会被释放
}
  • _lock 是一个引用类型的对象,作为锁的标识。
  • 使用 try-finally 块是为了确保即使发生异常,锁也能被正确释放。

四、Monitor的工作原理

Monitor 类的核心思想是基于锁对象的互斥机制:

  1. 当线程调用 Monitor.Enter(object) 时,它会尝试获取指定对象的锁。如果锁已被其他线程占用,则当前线程会被挂起,直到锁可用。
  2. 当线程调用 Monitor.Exit(object) 时,它会释放锁,允许其他线程获取该锁。
  3. Monitor.Wait 和 Monitor.Pulse 则用于实现线程间的信号传递,允许线程在特定条件下暂停或恢复执行。

五、使用示例1-保护共享变量

下面是一个使用 Monitor 类保护共享变量的例子:

using System;
using System.Threading;class Program
{private static int _counter = 0;private static readonly object _lock = new object();static void Main(){Thread t1 = new Thread(IncrementCounter);Thread t2 = new Thread(IncrementCounter);t1.Start();t2.Start();t1.Join();t2.Join();Console.WriteLine($"Final Counter Value: {_counter}");}static void IncrementCounter(){for (int i = 0; i < 100000; i++){Monitor.Enter(_lock);try{_counter++;}finally{Monitor.Exit(_lock);}}}
}

解释:

  • _lock 是一个静态对象,用于标识锁。
  • 每次访问 _counter 时,都会通过 Monitor.Enter 获取锁,并通过 Monitor.Exit 释放锁。
  • 最终输出的结果是 200000,因为所有线程的操作都被正确同步了。

六、使用示例2-线程间信号传递

Monitor 类还可以用于实现线程间的信号传递。以下是一个典型的生产者-消费者模型示例:

using System;
using System.Threading;class Program
{private static readonly object _lock = new object();private static bool _isReady = false; // 共享的状态变量static void Main(string[] args){Thread threadA = new Thread(DoWorkA);Thread threadB = new Thread(DoWorkB);threadA.Start();threadB.Start();threadA.Join();threadB.Join();}static void DoWorkA(){lock (_lock){Console.WriteLine("Thread A: Waiting for signal...");// 等待条件满足while (!_isReady){Monitor.Wait(_lock); // 释放锁并等待}Console.WriteLine("Thread A: Received signal. Continuing work.");}}static void DoWorkB(){Thread.Sleep(2000); // 模拟一些工作lock (_lock){Console.WriteLine("Thread B: Preparing to signal...");// 设置条件为 true_isReady = true;// 通知等待的线程Monitor.Pulse(_lock);Console.WriteLine("Thread B: Signal sent.");}}
}

解释:

  • 线程 A 调用 Monitor.Wait 释放锁并进入等待状态,直到线程 B 调用 Monitor.Pulse 通知它继续执行。
  • 这种机制非常适合需要线程间协作的场景。

七、注意事项

  1. 锁对象的选择

    • 锁对象必须是引用类型(如 object),不能是值类型(如 int)。
    • 推荐使用专用的私有对象作为锁对象,避免与其他代码发生冲突。
    • 不要使用 this 或字符串常量作为锁对象,以免引发死锁。
  2. 避免死锁

    • 死锁是指多个线程互相等待对方释放锁,导致程序无法继续运行。
    • 避免死锁的方法包括:
      • 确保锁的获取顺序一致。
      • 尽量减少锁的范围。
  3. 性能影响

    • 使用 Monitor 会导致线程阻塞,从而影响性能。
    • 对于高并发场景,可以考虑使用其他同步机制(如 SemaphoreSlim 或 ReaderWriterLockSlim)。

八、总结

Monitor 类是 C# 中实现线程互斥的一种重要工具,提供了比 lock 更灵活的控制能力。尽管它的使用稍微复杂一些,但能够满足更多高级需求,例如线程间的信号传递。

在实际开发中,选择合适的同步机制非常重要。对于简单的线程互斥场景,lock 可能更为直观;而对于需要更细粒度控制的场景,Monitor 则是一个不错的选择。

 

相关文章:

  • 华为数字化转型“三阶十二步法“:战略驱动、系统布局与敏捷落地的实践框架
  • spark和Hadoop的区别与联系
  • 前端框架开发编译阶段与运行时的核心内容详解Tree Shaking核心实现原理详解
  • 主流大模型(如OpenAI、阿里云通义千问、Anthropic、Hugging Face等)调用不同API的参数说明及对比总结
  • 解决方案评测|告别复杂配置!基于阿里云云原生应用开发平台CAP快速部署Bolt.diy
  • springboot对接阿里云大模型
  • 红队专题-漏洞挖掘-代码审计-反序列化
  • Semaphore的核心机制
  • Linux `init 6` 相关命令的完整使用指南
  • Java之封装(学习笔记)
  • Redis 哨兵与集群脑裂问题详解及解决方案
  • string类(详解)
  • 【AI+HR实战应用】用DeepSeek提升HR工作效能
  • STM32时钟树
  • firewall指令
  • 面试常用基础算法
  • MySQL 线上大表 DDL 如何避免锁表(pt-online-schema-change)
  • 宝马2016款泰产F800GS更换前减震油封和防尘盖
  • 致迈协创C1pro考勤系统简介
  • 立体匹配模型RAFT-Stereo的onnx导出与trt使用指南
  • 蔚来第三品牌萤火虫上市:对标宝马MINI,预期贡献10%销量
  • 全球南方声势卓然壮大的历史逻辑——写在万隆会议召开70周年之际
  • 马上评|机器人马拉松,也是具身智能产业的加速跑
  • 建投读书会·东西汇流|西风东渐中的上海营造
  • 非法收受财物2.29亿余元,窦万贵受贿案一审开庭
  • 硅基世界的“缘分”——系统与人工智能携手进化