聊透多线程编程-线程互斥与同步-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 则是一个不错的选择。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/78434.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第R4周:LSTM-火灾温度预测

文章目录 一、前期准备工作1.导入数据2. 数据集可视化 二、构建数据集1. 数据集预处理2. 设置X, y3. 划分数据集 三、模型训练1. 构建模型2. 定义训练函数3. 定义测试函数4. 正式训练模型 四、模型评估1. Loss图片2. 调用模型进行预测3. R2值评估 总结&#xff1a; &#x1f36…

toCharArray作用

toCharArray() 是 Java 中 String 类的一个方法&#xff0c;其作用是将字符串对象转换为一个字符数组。下面为你详细介绍其用法、原理和示例。 方法定义 toCharArray() 方法在 java.lang.String 类里被定义&#xff0c;方法签名如下 public char[] toCharArray() 此方法没有…

STM32八股【6】-----CortexM3的双堆栈(MSP、PSP)设计

STM32的线程模式&#xff08;Thread Mode&#xff09;和内核模式&#xff08;Handler Mode&#xff09;以及其对应的权级和堆栈指针 线程模式&#xff1a; 正常代码执行时的模式&#xff08;如 main 函数、FreeRTOS任务&#xff09; 可以是特权级&#xff08;使用MSP&#xff…

驱动支持的最高CUDA版本与实际安装的Runtime版本

查看电脑上安装的CUDA版本的多种方法&#xff0c;适用于不同系统和场景。 方法一&#xff1a;通过命令行工具 1. 查看CUDA Driver API版本&#xff08;显卡驱动支持的CUDA版本&#xff09; 命令&#xff1a;nvidia-smi操作&#xff1a; 打开终端&#xff08;Windows为CMD/Pow…

Python CT图像预处理——基于ITK-SNAP

Python CT图像预处理——nii格式读取、重采样、窗宽窗位设置_python读取nii-CSDN博客 基于原文指出以下几个问题&#xff1a;文件路径设置模糊&#xff1b;nilabel里面使用的get_data() 方法已经过时&#xff1b;需要导入scikit-image&#xff0c;还要导入一个matplotlib。 一…

【MQ篇】RabbitMQ之消息持久化!

目录 一、 交换机持久化 (Exchange Persistence)二、 队列持久化 (Queue Persistence)三、 消息持久化 (Message Persistence)四、 持久化的“黄金三角” &#x1f531;&#xff1a;三者缺一不可&#xff01;五、 来&#xff0c;完整的代码示例&#xff08;整合持久化和确认机制…

[AI技术(二)]JSONRPC协议MCPRAGAgent

Agent概述(一) AI技术基础(一) JSON-RPC 2.0 协议详解 JSON-RPC 2.0 是一种基于 JSON 的轻量级远程过程调用(RPC)协议,旨在简化跨语言、跨平台的远程通信。以下从协议特性、核心结构、错误处理、批量请求等角度进行详细解析: 一、协议概述 1. 设计原则 • 简单性:…

LeetCode238_除自身以外数组的乘积

LeetCode238_除自身以外数组的乘积 标签&#xff1a;#数组 #前缀和Ⅰ. 题目Ⅱ. 示例0. 个人方法一&#xff1a;暴力循环嵌套0. 个人方法二&#xff1a;前缀和后缀分别求积 标签&#xff1a;#数组 #前缀和 Ⅰ. 题目 给你一个整数数组 nums&#xff0c;返回 数组 answer &#…

算法笔记.spfa算法(bellman-ford算法的改进)

题目&#xff1a;&#xff08;来源于AcWing&#xff09; 给定一个 n 个点 m 条边的有向图&#xff0c;图中可能存在重边和自环&#xff0c; 边权可能为负数。 请你求出 1 号点到 n 号点的最短距离&#xff0c;如果无法从 1 号点走到 n 号点&#xff0c;则输出 impossible。 …

07 Python 字符串全解析

文章目录 一. 字符串的定义二. 字符串的基本用法1. 访问字符串中的字符2. 字符串切片3. 字符串拼接4. 字符串重复5.字符串比较6.字符串成员运算 三. 字符串的常用方法1. len() 函数2. upper() 和 lower() 方法3. strip() 方法4. replace() 方法5. split() 方法 四. 字符串的进阶…

Java集成Zxing和OpenCV实现二维码生成与识别工具类

Java集成Zxing和OpenCV实现二维码生成与识别工具类 本文将介绍如何使用Java集成Zxing和OpenCV库&#xff0c;实现二维码的生成和识别功能。识别方法支持多种输入形式&#xff0c;包括File对象、文件路径和Base64编码。 一、环境准备 添加Maven依赖 <dependencies><…

【专题刷题】二分查找(二)

&#x1f4dd;前言说明&#xff1a; 本专栏主要记录本人的基础算法学习以及LeetCode刷题记录&#xff0c;按专题划分每题主要记录&#xff1a;&#xff08;1&#xff09;本人解法 本人屎山代码&#xff1b;&#xff08;2&#xff09;优质解法 优质代码&#xff1b;&#xff…

Java—ThreadLocal底层实现原理

首先&#xff0c;ThreadLocal 本身并不提供存储数据的功能&#xff0c;当我们操作 ThreadLocal 的时候&#xff0c;实际上操作线程对象的一个名为 threadLocals 成员变量。这个成员变量的类型是 ThreadLocal 的一个内部类 ThreadLocalMap&#xff0c;它是真正用来存储数据的容器…

Elasticsearch(ES)中的脚本(Script)

文章目录 一. 脚本是什么&#xff1f;1. lang&#xff08;脚本语言&#xff09;2. source&#xff08;脚本代码&#xff09;3. params&#xff08;参数&#xff09;4. id&#xff08;存储脚本的标识符&#xff09;5. stored&#xff08;是否为存储脚本&#xff09;6. script 的…

客户联络中心能力与客户匹配方式

在数字化时代&#xff0c;客户联络中心作为企业与客户沟通的核心枢纽&#xff0c;其服务能力与客户需求的精准匹配至关重要。随着客户期望的不断提升&#xff0c;传统的“一刀切”服务模式已难以满足个性化需求&#xff0c;如何通过智能化的手段实现服务能力与客户的高效匹配&a…

深入理解网络原理:UDP协议详解

在计算机网络中&#xff0c;数据的传输是通过各种协议实现的&#xff0c;其中用户数据报协议&#xff08;UDP&#xff0c;User Datagram Protocol&#xff09;作为一种重要的传输层协议&#xff0c;广泛应用于实时通信、视频流、在线游戏等场景。本文将深入探讨UDP协议的特性、…

vscode切换Python环境

跑深度学习项目通常需要切换python环境&#xff0c;下面介绍如何在vscode切换python环境&#xff1a; 1.点击vscode界面左上角 2.在弹出框选择对应kernel

【MCP Node.js SDK 全栈进阶指南】中级篇(4):MCP错误处理与日志系统

前言 随着MCP应用的规模和复杂性增长,错误处理与日志系统的重要性也日益凸显。一个健壮的错误处理策略和高效的日志系统不仅可以帮助开发者快速定位和解决问题,还能提高应用的可靠性和可维护性。本文作为中级篇的第四篇,将深入探讨MCP TypeScript-SDK中的错误处理与日志系统…

【Qt】文件

&#x1f308; 个人主页&#xff1a;Zfox_ &#x1f525; 系列专栏&#xff1a;Qt 目录 一&#xff1a;&#x1f525; Qt 文件概述 二&#xff1a;&#x1f525; 输入输出设备类 三&#xff1a;&#x1f525; 文件读写类 四&#xff1a;&#x1f525; 文件和目录信息类 五&…

代码随想录算法训练营第五十八天 | 1.拓扑排序精讲 2.dijkstra(朴素版)精讲 卡码网117.网站构建 卡码网47.参加科学大会

1.拓扑排序精讲 题目链接&#xff1a;117. 软件构建 文章讲解&#xff1a;代码随想录 思路&#xff1a; 把有向无环图进行线性排序的算法都可以叫做拓扑排序。 实现拓扑排序的算法有两种&#xff1a;卡恩算法&#xff08;BFS&#xff09;和DFS&#xff0c;以下BFS的实现思…