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

news2025/4/26 10:17:56

目录

一、什么是临界区?

二、Mutex类简介

三、Mutex的基本用法

解释:

四、Mutex的工作原理

五、使用示例1-保护共享资源

解释:

六、使用示例2-跨进程同步

示例场景

1. 进程A - 主进程

2. 进程B - 第二个进程

输出结果

ProcessA 的输出

ProcessB 的输出

解释

七、注意事项

八、总结


 

在多线程编程中,线程之间的同步和互斥是确保程序正确运行的重要机制。C# 提供了多种工具来实现线程同步,其中 Mutex 是一种功能强大的同步原语,特别适合用于跨进程的线程互斥场景。本文将详细介绍如何使用 Mutex 类实现线程互斥,并通过示例展示其工作原理。


一、什么是临界区?

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

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


二、Mutex类简介

Mutex(Mutual Exclusion)类是 .NET 提供的一种线程同步工具,用于实现线程间的互斥访问。与 lockMonitor 不同,Mutex 支持跨进程的线程同步,这使得它非常适合用于多进程环境下的资源保护。

Mutex 的主要特点包括:

  • 跨进程支持Mutex 可以在不同进程之间共享,适用于分布式或多进程应用。
  • 独占锁:同一时间只有一个线程(或进程)可以持有 Mutex
  • 命名支持:可以通过命名方式创建全局 Mutex,从而实现跨进程同步。

三、Mutex的基本用法

Mutex 的基本用法包括以下几个步骤:

  1. 创建 Mutex 对象。
  2. 调用 WaitOne 方法获取锁。
  3. 执行需要同步的代码块。
  4. 调用 ReleaseMutex 方法释放锁。

为了确保资源的正确释放,通常会将 Mutex 放入 using 块中,这样即使发生异常,也能保证资源被正确释放。

using System;
using System.Threading;

class Program
{
    private static readonly Mutex _mutex = new Mutex(); // 创建 Mutex 对象

    static void Main()
    {
        Thread t1 = new Thread(DoWork);
        Thread t2 = new Thread(DoWork);

        t1.Start();
        t2.Start();

        t1.Join();
        t2.Join();
    }

    static void DoWork()
    {
        Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}: Waiting for mutex...");
        _mutex.WaitOne(); // 获取锁

        try
        {
            Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}: Entered critical section.");
            Thread.Sleep(2000); // 模拟一些工作
        }
        finally
        {
            _mutex.ReleaseMutex(); // 释放锁
            Console.WriteLine($"{Thread.CurrentThread.ManagedThreadId}: Released mutex.");
        }
    }
}

解释:

  • _mutex.WaitOne():尝试获取锁。如果锁已被占用,则当前线程会被阻塞,直到锁可用。
  • _mutex.ReleaseMutex():释放锁,允许其他线程获取该锁。
  • 使用 try-finally 块是为了确保即使发生异常,锁也能被正确释放。

四、Mutex的工作原理

Mutex 的核心思想是基于操作系统级别的信号量机制,提供了更高级别的同步能力:

  1. 当线程调用 WaitOne 方法时,它会尝试获取 Mutex。如果 Mutex 已被其他线程占用,则当前线程会被挂起,直到 Mutex 可用。
  2. 当线程调用 ReleaseMutex 方法时,它会释放 Mutex,允许其他线程获取该锁。
  3. 如果 Mutex 是命名的(通过构造函数指定名称),它可以跨进程共享,从而实现跨进程同步。

五、使用示例1-保护共享资源

下面是一个使用 Mutex 类保护共享资源的例子:

using System;
using System.Threading;

class Program
{
    private static int _counter = 0;
    private static readonly Mutex _mutex = new Mutex();

    static void Main()
    {
        Thread t1 = new Thread(IncrementCounter);
        Thread t2 = new Thread(IncrementCounter);

        t1.Start();
        t2.Start();

        t1.Join();
        t2.Join();

        _mutex.Dispose();

        Console.WriteLine($"Final Counter Value: {_counter}");
    }

    static void IncrementCounter()
    {
        for (int i = 0; i < 100000; i++)
        {
            _mutex.WaitOne();
            try
            {
                _counter++;
            }
            finally
            {
                _mutex.ReleaseMutex();
            }
        }
    }
}

解释:

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

六、使用示例2-跨进程同步

Mutex 的跨进程同步能力使其非常适合用于分布式或多进程环境中的资源共享和互斥访问。下面通过一个完整的例子,演示如何使用命名的 Mutex 来实现跨进程同步。

示例场景

假设我们有两个独立的应用程序(进程),它们都需要访问一个共享资源(例如文件或数据库)。为了避免数据竞争,我们需要确保同一时间只有一个进程可以访问该资源。我们将使用命名的 Mutex 来实现这一目标。

1. 进程A - 主进程

这是第一个应用程序,它尝试获取 Mutex 并独占访问共享资源。

// ProcessA.cs
using System;
using System.Threading;

class Program
{
    static void Main(string[] args)
    {
        // 创建一个命名的 Mutex
        bool isCreatedNew; // 是否是第一个创建 Mutex 的进程
        using (Mutex mutex = new Mutex(true, "Global\\SharedResourceMutex", out isCreatedNew))
        {
            if (isCreatedNew)
            {
                Console.WriteLine("Process A: This process owns the mutex.");
                Console.WriteLine("Process A: Accessing shared resource...");

                // 模拟对共享资源的操作
                Thread.Sleep(50000); // 假设操作需要 5 秒                

                Console.WriteLine("Process A: Releasing mutex.");

                //释放锁
                mutex.ReleaseMutex();
            }
            else
            {
                Console.WriteLine("Process A: Another process already owns the mutex. Waiting...");

                // 等待其他进程释放 Mutex
                mutex.WaitOne();
                Console.WriteLine("Process A: Acquired mutex after waiting.");

                // 模拟对共享资源的操作
                Console.WriteLine("Process A: Accessing shared resource...");
                Thread.Sleep(5000);

                Console.WriteLine("Process A: Releasing mutex.");

                //释放锁
                mutex.ReleaseMutex();
            }
        }
    }
}

2. 进程B - 第二个进程

这是第二个应用程序,它也会尝试获取同一个 Mutex,并访问共享资源。

// ProcessB.cs
using System;
using System.Threading;

class Program
{
    static void Main(string[] args)
    {
        // 创建一个命名的 Mutex
        bool isCreatedNew; // 是否是第一个创建 Mutex 的进程
        using (Mutex mutex = new Mutex(true, "Global\\SharedResourceMutex", out isCreatedNew))
        {
            try
            {
                if (isCreatedNew)
                {
                    Console.WriteLine("Process B: This process owns the mutex.");
                    Console.WriteLine("Process B: Accessing shared resource...");

                    // 模拟对共享资源的操作
                    Thread.Sleep(5000); // 假设操作需要 5 秒

                    Console.WriteLine("Process B: Releasing mutex.");

                    //释放锁
                    mutex.ReleaseMutex();
                }
                else
                {
                    Console.WriteLine("Process B: Another process already owns the mutex. Waiting...");

                    // 等待其他进程释放 Mutex
                    mutex.WaitOne();
                    Console.WriteLine("Process B: Acquired mutex after waiting.");

                    // 模拟对共享资源的操作
                    Console.WriteLine("Process B: Accessing shared resource...");
                    Thread.Sleep(5000);

                    Console.WriteLine("Process B: Releasing mutex.");

                    //释放锁
                    mutex.ReleaseMutex();
                }
            }
            catch (AbandonedMutexException)
            {
                Console.WriteLine("Process B: Detected an abandoned mutex. Continuing execution...");
                // 即使检测到被遗弃的 Mutex,当前线程仍然可以继续执行。
            }
        }
    }
}

输出结果

ProcessA 的输出

Process A: This process owns the mutex.
Process A: Accessing shared resource...
Process A: Releasing mutex.

ProcessB 的输出

Process B: Another process already owns the mutex. Waiting...
Process B: Acquired mutex after waiting.
Process B: Accessing shared resource...
Process B: Releasing mutex.

解释

  1. 命名的 Mutex

    • 在 new Mutex(true, "Global\\SharedResourceMutex", out isCreatedNew) 中,"Global\\SharedResourceMutex" 是 Mutex 的名称。
    • Global\\ 前缀表示该 Mutex 是全局的,可以在不同进程之间共享。
  2. 跨进程同步

    • 当 ProcessA 创建 Mutex 时,isCreatedNew 为 true,表示它是第一个创建该 Mutex 的进程。
    • 当 ProcessB 尝试创建同名的 Mutex 时,isCreatedNew 为 false,表示该 Mutex 已存在,并由另一个进程持有。
  3. 等待与释放

    • 如果 Mutex 已被占用,调用 mutex.WaitOne() 会使当前线程阻塞,直到 Mutex 被释放。
    • 调用 mutex.ReleaseMutex() 会释放 Mutex,允许其他线程或进程获取它。

七、注意事项

  1. 命名冲突

    • 命名的 Mutex 必须具有唯一性,避免与其他应用程序发生冲突。
    • 可以使用 GUID 或特定的前缀来确保名称的唯一性。
  2. 性能开销

    • Mutex 是基于操作系统级别的同步机制,性能开销较大,尤其是在高并发场景下。
    • 对于单进程内的线程同步,推荐使用 lock 或 Monitor
  3. 死锁风险

    • 如果一个进程获取了 Mutex 但未释放,会导致其他进程永远无法获取锁。
    • 确保在 finally 块中调用 ReleaseMutex,避免因异常导致锁未释放。
  4. 权限问题

    • 在某些情况下,创建全局 Mutex 可能需要管理员权限,尤其是在 Windows 系统中。
  5. 为什么使用 using

    • 将 Mutex 放入 using 块中可以确保资源的正确释放,避免资源泄漏。
    • 即使发生异常,Dispose 方法也会被自动调用,保证资源管理的安全性和可靠性。

八、总结

Mutex 类是 C# 中实现线程互斥的一种重要工具,特别适合用于跨进程的线程同步场景。尽管它的使用稍微复杂一些,但能够满足更多高级需求,例如分布式系统中的资源保护。

在实际开发中,选择合适的同步机制非常重要。对于简单的线程互斥场景,lockMonitor 可能更为直观;而对于需要跨进程同步的场景,Mutex 则是一个不错的选择。通过合理利用 Mutex,我们可以有效避免数据竞争和资源冲突,确保多线程或多进程应用的稳定性和可靠性。

 

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

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

相关文章

c++进阶——类与继承

文章目录 继承继承的基本概念继承的基本定义继承方式继承的一些注意事项 继承类模板 基类和派生类之间的转换继承中的作用域派生类的默认成员函数默认构造函数拷贝构造赋值重载析构函数默认成员函数总结 不能被继承的类继承和友元继承与静态成员多继承及其菱形继承问题继承模型…

复杂地形越野机器人导航新突破!VERTIFORMER:数据高效多任务Transformer助力越野机器人移动导航

作者&#xff1a; Mohammad Nazeri 1 ^{1} 1, Anuj Pokhrel 1 ^{1} 1, Alexandyr Card 1 ^{1} 1, Aniket Datar 1 ^{1} 1, Garrett Warnell 2 , 3 ^{2,3} 2,3, Xuesu Xiao 1 ^{1} 1单位&#xff1a; 1 ^{1} 1乔治梅森大学计算机科学系&#xff0c; 2 ^{2} 2美国陆军研究实验室&…

Jsp技术入门指南【十】IDEA 开发环境下实现 MySQL 数据在 JSP 页面的可视化展示,实现前后端交互

Jsp技术入门指南【十】IDEA 开发环境下实现 MySQL 数据在 JSP 页面的可视化展示&#xff0c;实现前后端交互 前言一、JDBC 核心接口和类&#xff1a;数据库连接的“工具箱”1. 常用的 2 个“关键类”2. 必须掌握的 5 个“核心接口” 二、创建 JDBC 程序的步骤1. 第一步&#xf…

数据库未正常关闭后,再次启动时只有主进程,数据库日志无输出

瀚高数据库 目录 环境 症状 问题原因 解决方案 环境 系统平台&#xff1a;银河麒麟svs&#xff08;X86_64&#xff09; 版本&#xff1a;4.5.8 症状 现象&#xff1a;使用pg_ctl stop停止数据库&#xff0c;未正常关闭&#xff1b;使用pg_ctl stop -m i 强制关闭数据库后&…

Oracle Recovery Tools修复ORA-00742、ORA-600 ktbair2: illegal inheritance故障

接到客户反馈,一套运行在虚拟化平台中的Oracle数据库,由于机房断电,导致数据库无法启动,最初启动报错 2025-04-22T16:59:48.92222708:00 Completed: alter database mount exclusive alter database open 2025-04-22T16:59:52.60972608:00 Ping without log force is disabled:…

基于 Netmiko 的网络设备自动化操作

学习目标 掌握 Netmiko 库的核心功能与使用场景。能够通过 Netmiko 连接多厂商设备并执行命令和配置。实现批量设备管理、配置备份与自动化巡检。掌握异常处理、日志记录与性能优化技巧。理解 Netmiko 在自动化运维体系中的角色。 1. Netmiko 简介 1.1 什么是 Netmiko Netmi…

LeNet5 神经网络的参数解析和图片尺寸解析

1.LeNet-5 神经网络 以下是针对 LeNet-5 神经网络的详细参数解析和图片尺寸变化分析&#xff0c;和原始论文设计&#xff0c;通过分步计算说明各层的张量变换过程。 经典的 LeNet-5架构简化版&#xff08;原始论文输入为 32x32&#xff0c;MNIST 常用 28x28 需调整&#xff09…

Axure大屏可视化模板:多领域数据决策的新引擎

在数据驱动决策的时代&#xff0c;Axure大屏可视化模板凭借交互性与可定制性&#xff0c;成为农业、园区管理、智慧城市、企业及医疗领域的创新工具&#xff0c;助力高效数据展示与智能决策。 核心应用场景 1. 农业精细化&#xff1a;实时监控土壤湿度、作物生长曲线&#x…

代码随想录算法训练营第60期第十七天打卡

今天我们继续进入二叉树的下一个章节&#xff0c;今天的内容我在写今天的博客前大致看了一下部分题目难度不算大&#xff0c;那我们就进入今天的题目。 第一题对应力扣编号为654的题目最大二叉树 这道题目的坑相当多&#xff0c;我第一次题目没有看明白就是我不知道到底是如何…

SOC估算:开路电压修正的安时积分法

SOC估算&#xff1a;开路电压修正的安时积分法 基本概念 开路电压修正的安时积分法是一种结合了两种SOC估算方法的混合技术&#xff1a; 安时积分法&#xff08;库仑计数法&#xff09; - 通过电流积分计算SOC变化 开路电压法 - 通过电池电压与SOC的关系曲线进行校准 方法原…

使用 SSE + WebFlux 推送日志信息到前端

为什么使用 SSE 而不使用 WebSocket, 请看 SEE 对比 Websocket 的优缺点。 特性SSEWebSocket通信方向单向&#xff08;服务器→客户端&#xff09;双向&#xff08;全双工&#xff09;协议基于 HTTP独立协议&#xff08;需 ws:// 前缀&#xff09;兼容性现代浏览器&#xff08…

二叉树的遍历(广度优先搜索)

二叉树的第二种遍历方式&#xff0c;层序遍历&#xff0c;本质是运用队列对二叉树进行搜索。 层序遍历是指将二叉树的每一层按顺序遍历&#xff0c;通过队列实现就是先将根节点push入队&#xff0c;统计此时的队列中的元素数量size&#xff0c;将size元素全部pop出去&#xff0…

2025年计算机视觉与智能通信国际会议(ICCVIC 2025)

2025 International Conference on Computer Vision and Intelligent Communication 一、大会信息 会议简称&#xff1a;ICCVIC 2025 大会地点&#xff1a;中国杭州 收录检索&#xff1a;提交Ei Compendex,CPCI,CNKI,Google Scholar等 二、会议简介 2025年计算机视觉与智能通…

手工收集统计信息

有时想对某些表收集统计信息 CREATE OR REPLACE PROCEDURE GATHER_STATS ASDECLAREV_SQL1 VARCHAR(1000);--表游标CURSOR C1 ISSELECT (SELECT USER) AS TABLE_OWNER,TABLE_NAMEFROM USER_TABLES; --可以在这里加过滤条件--索引游标CURSOR C2 ISSELECT TABLE_OWNER,INDEX_NAM…

flume整合Kafka和spark-streaming核心编程

flume整合Kafka 需求1&#xff1a;利用flume监控某目录中新生成的文件&#xff0c;将监控到的变更数据发送给kafka&#xff0c;kafka将收到的数据打印到控制台&#xff1a; 1.查看topic 2.编辑flume-Kafka.conf&#xff0c;并启动flume 3.启动Kafka消费者 4.新增测试数据 5.查…

EDI 如何与 ERP,CRM,WMS等系统集成

在数字化浪潮下&#xff0c;与制造供应链相关产业正加速向智能化供应链转型。传统人工处理订单、库存和物流的方式已难以满足下单客户对响应速度和数据准确性的严苛要求。EDI技术作为企业间数据交换的核心枢纽&#xff0c;其与ERP、CRM、WMS等业务系统的深度集成&#xff0c;成…

面试踩过的坑

1、 “”和equals 的区别 “”是运算符&#xff0c;如果是基本数据类型&#xff0c;则比较存储的值&#xff1b;如果是引用数据类型&#xff0c;则比较所指向对象的地址值。equals是Object的方法&#xff0c;比较的是所指向的对象的地址值&#xff0c;一般情况下&#xff0c;重…

多物理场耦合低温等离子体装置求解器PASSKEy2

文章目录 PASSKEy2简介PASSKEY2计算流程PASSKEy2 中求解的物理方程电路模型等离子体模型燃烧模型 PASSKEy2的使用 PASSKEy2简介 PASSKEy2 是在 PASSKEy1 的基础上重新编写的等离子体数值模拟程序。 相较于 PASSKEy1&#xff0c; PASSKEy2 在具备解决低温等离子体模拟问题的能力…

视频噪点多,如何去除画面噪点?

你是否遇到过这样的困扰&#xff1f;辛辛苦苦拍摄的视频&#xff0c;导出后却满屏 “雪花”&#xff0c;夜景变 “噪点盛宴”&#xff0c;低光环境秒变 “马赛克现场”&#xff1f; 无论是日常拍摄的vlog、珍贵的家庭录像&#xff0c;还是专业制作的影视作品&#xff0c;噪点问…

09前端项目----分页功能

分页功能 分页器的优点实现分页功能自定义分页器先实现静态分页器调试分页器动态数据/交互 Element UI组件 分页器的优点 电商平台同时展示的数据很多&#xff0c;所以采用分页功能实现分页功能 Element UI已经有封装好的组件&#xff0c;但是也要掌握原理&#xff0c;以及自定…