C#多线程(4):进程同步Mutex类

简介:

C#多线程(4):进程同步Mutex类

目录
Mutex 类
构造函数和方法
系统只能运行一个程序的实例
解释一下上面的示例
接替运行
进程同步示例
另外
Mutex 类
Mutex 中文为互斥,Mutex 类叫做互斥锁。它还可用于进程间同步的同步基元。

Mutex 跟 lock 相似,但是 Mutex 支持多个进程。Mutex 大约比 lock 慢 20 倍。

互斥锁(Mutex),用于多线程中防止两条线程同时对一个公共资源进行读写的机制。

Windows 操作系统中,Mutex 同步对象有两个状态:

signaled:未被任何对象拥有;
nonsignaled:被一个线程拥有;
Mutex 只能在获得锁的线程中,释放锁。

构造函数和方法
Mutex 类其构造函数如下:

构造函数 说明
Mutex() 使用默认属性初始化 Mutex类的新实例。
Mutex(Boolean) 使用 Boolean 值(指示调用线程是否应具有互斥体的初始所有权)初始化 Mutex 类的新实例。
Mutex(Boolean, String) 使用 Boolean 值(指示调用线程是否应具有互斥体的初始所有权以及字符串是否为互斥体的名称)初始化 Mutex 类的新实例。
Mutex(Boolean, String, Boolean) 使用可指示调用线程是否应具有互斥体的初始所有权以及字符串是否为互斥体的名称的 Boolean 值和当线程返回时可指示调用线程是否已赋予互斥体的初始所有权的 Boolean 值初始化 Mutex 类的新实例。
Mutex 对于进程同步有所帮助,例如其应用场景主要是控制系统只能运行一个此程序的实例。

Mutex 构造函数中的 String类型参数 叫做互斥量而互斥量是全局的操作系统对象。
Mutex 只要考虑实现进程间的同步,它会耗费比较多的资源,进程内请考虑 Monitor/lock。
Mutex 的常用方法如下:

方法 说明
Close() 释放由当前 WaitHandle 占用的所有资源。
Dispose() 释放由 WaitHandle 类的当前实例占用的所有资源。
OpenExisting(String) 打开指定的已命名的互斥体(如果已经存在)。
ReleaseMutex() 释放 Mutex一次。
TryOpenExisting(String, Mutex) 打开指定的已命名的互斥体(如果已经存在),并返回指示操作是否成功的值。
WaitOne() 阻止当前线程,直到当前 WaitHandle 收到信号。
WaitOne(Int32) 阻止当前线程,直到当前 WaitHandle 收到信号,同时使用 32 位带符号整数指定时间间隔(以毫秒为单位)。
WaitOne(Int32, Boolean) 阻止当前线程,直到当前的 WaitHandle 收到信号为止,同时使用 32 位带符号整数指定时间间隔,并指定是否在等待之前退出同步域。
WaitOne(TimeSpan) 阻止当前线程,直到当前实例收到信号,同时使用 TimeSpan 指定时间间隔。
WaitOne(TimeSpan, Boolean) 阻止当前线程,直到当前实例收到信号为止,同时使用 TimeSpan 指定时间间隔,并指定是否在等待之前退出同步域。
关于 Mutex 类,我们可以先通过几个示例去了解它。

系统只能运行一个程序的实例
下面是一个示例,用于控制系统只能运行一个此程序的实例,不允许同时启动多次。

class Program
{
    // 第一个程序
    const string name = "www.whuanle.cn";
    private static Mutex m;
    static void Main(string[] args)
    {
        // 本程序是否是 Mutex 的拥有者
        bool firstInstance;
        m = new Mutex(false,name,out firstInstance);
        if (!firstInstance)
        {
            Console.WriteLine("程序已在运行!按下回车键退出!");
            Console.ReadKey();
            return;
        }
        Console.WriteLine("程序已经启动");
        Console.WriteLine("按下回车键退出运行");
        Console.ReadKey();
        m.ReleaseMutex();
        m.Close();
        return;
    }
}

上面的代码中,有些地方前面没有讲,没关系,我们运行一下生成的程序先。

解释一下上面的示例
Mutex 的工作原理:

当两个或两个以上的线程同时访问共享资源时,操作系统需要一个同步机制来确保每次只有一个线程使用资源。

Mutex 是一种同步基元,Mutex 仅向一个线程授予独占访问共享资源的权限。这个权限依据就是 互斥体,当一个线程获取到互斥体后,其它线程也在试图获取互斥体时,就会被挂起(阻塞),直到第一个线程释放互斥体。

对应我们上一个代码示例中,实例化 Mutex 类的构造函数如下:

m = new Mutex(false,name,out firstInstance);
其构造函数原型如下:

public Mutex (bool initiallyOwned, string name, out bool createdNew);

前面我们提出过,Mutex 对象有两种状态,signaled 和 nonsignaled。

通过 new 来实例化 Mutex 类,会检查系统中此互斥量 name 是否已经被使用,如果没有被使用,则会创建 name 互斥量并且此线程拥有此互斥量的使用权;此时 createdNew == true。

那么 initiallyOwned ,它的作用是是否允许线程是否能够获取到此互斥量的初始化所有权。因为我们希望只有一个程序能够在后台运行,因此我们要设置为 false。

驱动开发中关于Mutex :https://docs.microsoft.com/zh-cn/windows-hardware/drivers/kernel/introduction-to-mutex-objects

对了, Mutex 的 参数中,name 是非常有讲究的。

在运行终端服务的服务器上,命名系统 mutex 可以有两个级别的可见性。

如果其名称以前缀 "Global" 开头,则 mutex 在所有终端服务器会话中可见。
如果其名称以前缀 "Local" 开头,则 mutex 仅在创建它的终端服务器会话中可见。 在这种情况下,可以在服务器上的其他每个终端服务器会话中存在具有相同名称的单独 mutex。
如果在创建已命名的 mutex 时未指定前缀,则采用前缀 "Local"。 在终端服务器会话中,两个互斥体的名称只是它们的前缀不同,它们都是对终端服务器会话中的所有进程都可见。

也就是说,前缀名称 "Global" 和 "Local" 描述互斥体名称相对于终端服务器会话的作用域,而不是相对于进程。

请参考:

https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.mutex?view=netcore-3.1#methods

https://www.cnblogs.com/suntp/p/8258488.html

接替运行
这里要实现,当同时点击一个程序时,只能有一个实例A可以运行,其它实例进入等待队列,等待A运行完毕后,然后继续运行队列中的下一个实例。

我们将每个程序比作一个人,模拟一个厕所坑位,每次只能有一个人上厕所,其他人需要排队等候。

使用 WaitOne() 方法来等待别的进程释放互斥量,即模拟排队;ReleaseMutex() 方法解除对坑位的占用。

class Program
{
    // 第一个程序
    const string name = "www.whuanle.cn";
    private static Mutex m;
    static void Main(string[] args)
    {
        // wc 还有没有位置
        bool firstInstance;
        m = new Mutex(true,name,out firstInstance);

        // 已经有人在上wc
        if (!firstInstance)
        {
            // 等待运行的实例退出,此进程才能运行。
            Console.WriteLine("排队等待");
            m.WaitOne();
            GoWC();
            return;
        }
        GoWC();

        return;
    }

    private static void GoWC()
    {
        Console.WriteLine(" 开始上wc");
        Thread.Sleep(1000);
        Console.WriteLine(" 开门");
        Thread.Sleep(1000);
        Console.WriteLine(" 关门");
        Thread.Sleep(1000);
        Console.WriteLine(" xxx");
        Thread.Sleep(1000);
        Console.WriteLine(" 开门");
        Thread.Sleep(1000);
        Console.WriteLine(" 离开wc");
        m.ReleaseMutex();
        Thread.Sleep(1000);
        Console.WriteLine(" 洗手");
    }
}

此时,我们使用了

        m = new Mutex(true,name,out firstInstance);

一个程序结束后,要允许其它线程能够创建 Mutex 对象获取互斥量,需要将构造函数的第一个参数设置为 true。

你也可以改成 false,看看会报什么异常。

你可以使用 WaitOne(Int32) 来设置等待时间,单位是毫秒,超过这个时间就不排队了,去别的地方上厕所。

为了避免出现问题,请考虑在 finally 块中执行 m.ReleaseMutex()。

进程同步示例
这里我们实现一个这样的场景:

父进程 Parant 启动子进程 Children ,等待子进程 Children 执行完毕,子进程退出,父进程退出。

新建一个 .NET Core 控制台项目,名称为 Children,其 Progarm 中的代码如下

using System;
using System.Threading;

namespace Children
{

class Program
{
    const string name = "进程同步示例";
    private static Mutex m;
    static void Main(string[] args)
    {
        Console.WriteLine("子进程被启动...");
        bool firstInstance;

        // 子进程创建互斥体
        m = new Mutex(true, name, out firstInstance);

        // 按照我们设计的程序,创建一定是成功的
        if (firstInstance)
        {
            Console.WriteLine("子线程执行任务");
            DoWork();
            Console.WriteLine("子线程任务完成");

            // 释放互斥体
            m.ReleaseMutex();
            // 结束程序
            return;
        }
        else
        {
            Console.WriteLine("莫名其妙的异常,直接退出");
        }
    }
    private static void DoWork()
    {
        for (int i = 0; i < 5; i++)
        {
            Console.WriteLine("子线程工作中");
            Thread.Sleep(TimeSpan.FromSeconds(1));
        }
    }
}

}

然后发布或生成项目,打开程序文件位置,复制线程文件路径。
创建一个新项目,名为 Parant 的 .NET Core 控制台,其 Program 中的代码如下:

using System;
using System.Diagnostics;
using System.Threading;

namespace Parant
{

class Program
{
    const string name = "进程同步示例";
    private static Mutex m;
    static void Main(string[] args)
    {
        // 晚一些再执行,我录屏要对正窗口位置
        Thread.Sleep(TimeSpan.FromSeconds(3));
        Console.WriteLine("父进程启动!");

        new Thread(() =>
        {
            // 启动子进程
            Process process = new Process();
            process.StartInfo.UseShellExecute = true;
            process.StartInfo.CreateNoWindow = false;
            process.StartInfo.WorkingDirectory = @"../../../ConsoleApp9\Children\bin\Debug\netcoreapp3.1";
            process.StartInfo.FileName = @"../../../ConsoleApp9\Children\bin\Debug\netcoreapp3.1\Children.exe";
            process.Start();
            process.WaitForExit();
        }).Start();
        // 子进程启动需要一点时间
        Thread.Sleep(TimeSpan.FromSeconds(1));

        // 获取互斥体
        bool firstInstance;
        m = new Mutex(true, name, out firstInstance);

        // 说明子进程还在运行
        if (!firstInstance)
        {
            // 等待子进程运行结束
            Console.WriteLine("等待子进程运行结束");
            m.WaitOne();
            Console.WriteLine("子进程运行结束,程序将在3秒后自动退出");
            m.ReleaseMutex();
            Thread.Sleep(TimeSpan.FromSeconds(3));
            return;
        }
    }
}

}

请将 Children 项目的程序文件路径,替换到 Parant 项目启动子进程的那部分字符串中。

然后启动 Parant.exe,可以观察到如下图的运行过程:

另外
构造函数中,如果为 name 指定 null 或空字符串,则将创建一个本地 Mutex 对象,只会在进程内有效。

Mutex 有些使用方法比较隐晦,可以参考 https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.mutex.-ctor?view=netcore-3.1#System_Threading_Mutex__ctor_System_Boolean_

另外打开互斥体,请参考

https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.mutex.openexisting?view=netcore-3.1

https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.mutex.tryopenexisting?view=netcore-3.1

到目前为止,我们学习了排他锁 lock、Monitor、Mutex。下一篇我们将来学习非排他锁定结构的Semaphore和SemaphoreSlim 。

原文地址https://www.cnblogs.com/whuanle/p/12726724.html

相关文章
|
7月前
|
安全 算法 Java
Java 多线程:线程安全与同步控制的深度解析
本文介绍了 Java 多线程开发的关键技术,涵盖线程的创建与启动、线程安全问题及其解决方案,包括 synchronized 关键字、原子类和线程间通信机制。通过示例代码讲解了多线程编程中的常见问题与优化方法,帮助开发者提升程序性能与稳定性。
331 0
|
4月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
247 1
|
4月前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
263 1
|
12月前
|
存储 Linux API
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
在计算机系统的底层架构中,操作系统肩负着资源管理与任务调度的重任。当我们启动各类应用程序时,其背后复杂的运作机制便悄然展开。程序,作为静态的指令集合,如何在系统中实现动态执行?本文带你一探究竟!
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
|
8月前
|
机器学习/深度学习 监控 算法
局域网行为监控软件 C# 多线程数据包捕获算法:基于 KMP 模式匹配的内容分析优化方案探索
本文探讨了一种结合KMP算法的多线程数据包捕获与分析方案,用于局域网行为监控。通过C#实现,该系统可高效检测敏感内容、管理URL访问、分析协议及审计日志。实验表明,相较于传统算法,KMP在处理大规模网络流量时效率显著提升。未来可在算法优化、多模式匹配及机器学习等领域进一步研究。
229 0
|
12月前
|
数据采集 Java 数据处理
Python实用技巧:轻松驾驭多线程与多进程,加速任务执行
在Python编程中,多线程和多进程是提升程序效率的关键工具。多线程适用于I/O密集型任务,如文件读写、网络请求;多进程则适合CPU密集型任务,如科学计算、图像处理。本文详细介绍这两种并发编程方式的基本用法及应用场景,并通过实例代码展示如何使用threading、multiprocessing模块及线程池、进程池来优化程序性能。结合实际案例,帮助读者掌握并发编程技巧,提高程序执行速度和资源利用率。
625 0
【JavaEE】——多线程常用类
Callable的call方法,FutureTask类,ReentrantLock可重入锁和对比,Semaphore信号量(PV操作)CountDownLatch锁存器,
|
Java 程序员 调度
【JavaEE】线程创建和终止,Thread类方法,变量捕获(7000字长文)
创建线程的五种方式,Thread常见方法(守护进程.setDaemon() ,isAlive),start和run方法的区别,如何提前终止一个线程,标志位,isinterrupted,变量捕获
|
11月前
|
Linux 数据库 Perl
【YashanDB 知识库】如何避免 yasdb 进程被 Linux OOM Killer 杀掉
本文来自YashanDB官网,探讨Linux系统中OOM Killer对数据库服务器的影响及解决方法。当内存接近耗尽时,OOM Killer会杀死占用最多内存的进程,这可能导致数据库主进程被误杀。为避免此问题,可采取两种方法:一是在OS层面关闭OOM Killer,通过修改`/etc/sysctl.conf`文件并重启生效;二是豁免数据库进程,由数据库实例用户借助`sudo`权限调整`oom_score_adj`值。这些措施有助于保护数据库进程免受系统内存管理机制的影响。
|
11月前
|
Linux Shell
Linux 进程前台后台切换与作业控制
进程前台/后台切换及作业控制简介: 在 Shell 中,启动的程序默认为前台进程,会占用终端直到执行完毕。例如,执行 `./shella.sh` 时,终端会被占用。为避免不便,可将命令放到后台运行,如 `./shella.sh &`,此时终端命令行立即返回,可继续输入其他命令。 常用作业控制命令: - `fg %1`:将后台作业切换到前台。 - `Ctrl + Z`:暂停前台作业并放到后台。 - `bg %1`:让暂停的后台作业继续执行。 - `kill %1`:终止后台作业。 优先级调整:
938 5