C#多线程系列之进程同步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.jb51.net/article/237313.htm

接替运行

这里要实现,当同时点击一个程序时,只能有一个实例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()

进程同步示例

这里我们实现一个这样的场景:

父进程 Parent 启动子进程 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));
            }
        }
    }
}

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

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

namespace Parent
{
    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 项目的程序文件路径,替换到 Parent 项目启动子进程的那部分字符串中。

然后启动 Parent.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

到此这篇关于C#多线程系列之进程同步Mutex类的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • C#多线程系列之资源池限制

    Semaphore.SemaphoreSlim 类 两者都可以限制同时访问某一资源或资源池的线程数. 这里先不扯理论,我们从案例入手,通过示例代码,慢慢深入了解. Semaphore 类 这里,先列出 Semaphore 类常用的 API. 其构造函数如下: 构造函数 说明 Semaphore(Int32, Int32) 初始化 Semaphore 类的新实例,并指定初始入口数和最大并发入口数. Semaphore(Int32, Int32, String) 初始化 Semaphore 类的新实

  • C#多线程系列之多阶段并行线程

    前言 这一篇,我们将学习用于实现并行任务.使得多个线程有序同步完成多个阶段的任务. 应用场景主要是控制 N 个线程(可随时增加或减少执行的线程),使得多线程在能够在 M 个阶段中保持同步. 线程工作情况如下: 我们接下来 将学习C# 中的 Barrier ,用于实现并行协同工作. Barrier 类 使多个任务能够采用并行方式依据某种算法在多个阶段中协同工作,使多个线程(称为“参与者” )分阶段同时处理算法. 可以使多个线程(称为“参与者” )分阶段同时处理算法.(注意算法这个词) 每个参与者完

  • C#多线程系列之手动线程通知

    区别与示例 AutoResetEvent 和 ManualResetEvent 十分相似.两者之间的区别,在于前者是自动(Auto),后者是手动(Manua). 你可以先运行下面的示例,再测试两者的区别. AutoResetEvent 示例: class Program { // 线程通知 private static AutoResetEvent resetEvent = new AutoResetEvent(false); static void Main(string[] args) {

  • C#多线程系列之线程完成数

    解决一个问题 假如,程序需要向一个 Web 发送 5 次请求,受网路波动影响,有一定几率请求失败.如果失败了,就需要重试. 示例代码如下: class Program { private static int count = 0; static void Main(string[] args) { for (int i = 0; i < 5; i++) new Thread(HttpRequest).Start(); // 创建线程 // 用于不断向另一个线程发送信号 while (count

  • C#多线程系列之读写锁

    本篇的内容主要是介绍 ReaderWriterLockSlim 类,来实现多线程下的读写分离. ReaderWriterLockSlim ReaderWriterLock 类:定义支持单个写线程和多个读线程的锁. ReaderWriterLockSlim 类:表示用于管理资源访问的锁定状态,可实现多线程读取或进行独占式写入访问. 两者的 API 十分接近,而且 ReaderWriterLockSlim 相对 ReaderWriterLock 来说 更加安全.因此本文主要讲解 ReaderWrit

  • C#多线程系列之线程的创建和生命周期

    目录 1,获取当前线程信息 2,管理线程状态 2.1 启动与参数传递 2.1.1 ParameterizedThreadStart 2.1.2 使用静态变量或类成员变量 2.1.3 委托与Lambda 2.2 暂停与阻塞 2.3 线程状态 2.4 终止 2.5 线程的不确定性 2.6 线程优先级.前台线程和后台线程 2.7 自旋和休眠 1,获取当前线程信息 Thread.CurrentThread 是一个 静态的 Thread 类,Thread 的CurrentThread 属性,可以获取到当前

  • C#多线程系列之多线程锁lock和Monitor

    目录 1,Lock lock 原型 lock 编写实例 2,Monitor 怎么用呢 解释一下 示例 设置获取锁的时效 1,Lock lock 用于读一个引用类型进行加锁,同一时刻内只有一个线程能够访问此对象.lock 是语法糖,是通过 Monitor 来实现的. Lock 锁定的对象,应该是静态的引用类型(字符串除外). 实际上字符串也可以作为锁的对象使用,只是由于字符串对象的特殊性,可能会造成不同位置的不同线程冲突.如果你能保证字符串的唯一性,例如 Guid 生成的字符串,也是可以作为锁的对

  • C#多线程系列之原子操作

    目录 知识点 竞争条件 线程同步 CPU时间片和上下文切换 阻塞 内核模式和用户模式 Interlocked 类 1,出现问题 2,Interlocked.Increment() 3,Interlocked.Exchange() 4,Interlocked.CompareExchange() 5,Interlocked.Add() 6,Interlocked.Read() 知识点 竞争条件 当两个或两个以上的线程访问共享数据,并且尝试同时改变它时,就发生争用的情况.它们所依赖的那部分共享数据,叫

  • C#多线程系列之线程通知

    AutoRestEvent 类用于从一个线程向另一个线程发送通知. 微软文档是这样介绍的:表示线程同步事件在一个等待线程释放后收到信号时自动重置. 其构造函数只有一个: 构造函数里面的参数用于设置信号状态. 构造函数 说明 AutoResetEvent(Boolean) 用一个指示是否将初始状态设置为终止的布尔值初始化 AutoResetEvent 类的新实例. 真糟糕的机器翻译. 常用方法 AutoRestEvent 类是干嘛的,构造函数的参数又是干嘛的?不着急,我们来先来看看这个类常用的方法

  • C#多线程系列之进程同步Mutex类

    Mutex 中文为互斥,Mutex 类叫做互斥锁.它还可用于进程间同步的同步基元. Mutex 跟 lock 相似,但是 Mutex 支持多个进程.Mutex 大约比 lock 慢 20 倍. 互斥锁(Mutex),用于多线程中防止两条线程同时对一个公共资源进行读写的机制. Windows 操作系统中,Mutex 同步对象有两个状态: signaled:未被任何对象拥有: nonsignaled:被一个线程拥有: Mutex 只能在获得锁的线程中,释放锁. 构造函数和方法 Mutex 类其构造函

  • c#互斥锁Mutex类用法介绍

    什么是Mutex “mutex”是术语“互相排斥(mutually exclusive)”的简写形式,也就是互斥量.互斥量跟临界区中提到的Monitor很相似,只有拥有互斥对象的线程才具有访问资源的权限,由于互斥对象只有一个,因此就决定了任何情况下此共享资源都不会同时被多个线程所访问.当前占据资源的线程在任务处理完后应将拥有的互斥对象交出,以便其他线程在获得后得以访问资源.互斥量比临界区复杂,因为使用互斥不仅仅能够在同一应用程序不同线程中实现资源的安全共享,而且可以在不同应用程序的线程之间实现对

  • C#多线程系列之任务基础(三)

    目录 TaskAwaiter 延续的另一种方法 另一种创建任务的方法 实现一个支持同步和异步任务的类型 Task.FromCanceled() 如何在内部取消任务 Yield 关键字 补充知识点 TaskAwaiter 先说一下 TaskAwaiter,TaskAwaiter 表示等待异步任务完成的对象并为结果提供参数. Task 有个 GetAwaiter() 方法,会返回TaskAwaiter 或TaskAwaiter<TResult>,TaskAwaiter 类型在 System.Run

  • C#多线程编程之使用ReaderWriterLock类实现多用户读与单用户写同步的方法

    本文实例讲述了C#多线程编程之使用ReaderWriterLock类实现多用户读与单用户写同步的方法.分享给大家供大家参考,具体如下: 摘要:C#提供了System.Threading.ReaderWriterLock类以适应多用户读/单用户写的场景.该类可实现以下功能:如果资源未被写操作锁定,那么任何线程都可对该资源进行读操作锁定,并且对读操作锁数量没有限制,即多个线程可同时对该资源进行读操作锁定,以读取数据. 使用Monitor或Mutex进行同步控制的问题:由于独占访问模型不允许任何形式的

随机推荐