C#多线程(4):进程同步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 只要考虑实现进程间的同步,它会耗费比较多的资源,进程内请考虑 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()
。
进程同步示例
这里我们实现一个这样的场景:
父进程 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
到目前为止,我们学习了排他锁 lock、Monitor、Mutex。下一篇我们将来学习非排他锁定结构的Semaphore
和SemaphoreSlim
。
C#多线程(4):进程同步Mutex类的更多相关文章
- C#使用Monitor类、Lock和Mutex类进行多线程同步
在多线程中,为了使数据保持一致性必须要对数据或是访问数据的函数加锁,在数据库中这是很常见的,但是在程序中由于大部分都是单线程的程序,所以没有加锁的必要,但是在多线程中,为了保持数据的同步,一定要加锁, ...
- C#中Monitor类、Lock关键字和Mutex类
线程:线程是进程的独立执行单元,每一个进程都有一个主线程,除了主线程可以包含其他的线程.多线程的意义:多线程有助于改善程序的总体响应性,提高CPU的效率.多线程的应用程序域是相当不稳定的,因为多个线程 ...
- 多线程锁:Mutex互斥体,Semaphore信号量,Monitor监视器,lock,原子操作InterLocked
Mutex类 “mutex”是术语“互相排斥(mutually exclusive)”的简写形式,也就是互斥量.互斥量跟临界区中提到的Monitor很相似,只有拥有互斥对象的线程才具有访问资源的权限, ...
- Java多线程之线程其他类
Java多线程之线程其他类 实际编码中除了前面讲到的常用的类之外,还有几个其他类也有可能用得到,这里来统一整理一下: 1,Callable接口和Future接口 JDK1.5以后提供了上面这2个接口, ...
- MFC关于多线程中传递窗口类指针时ASSERT_VALID出错的另类解决 转
MFC关于多线程中传递窗口类指针时ASSERT_VALID出错的另类解决 在多线程设计中,许多人为了省事,会将对话框类或其它类的指针传给工作线程,而在工作线程中调用该类的成员函数或成员变量等等. ...
- “全栈2019”Java多线程第二章:创建多线程之继承Thread类
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- c++多线程基础3(mutex)
整理自:zh.cppreference.com/w/cpp/thread 互斥锁 互斥算法避免多个线程同时访问共享资源.这会避免数据竞争,并提供线程间的同步支持.定义于头文件 <mutex> ...
- C#多线程---Mutex类实现线程同步
一.例子 1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 ...
- C# 多线程(lock,Monitor,Mutex,同步事件和等待句柄)
本篇从 Monitor,Mutex,ManualResetEvent,AutoResetEvent,WaitHandler 的类关系图开始,希望通过本篇的介绍能对常见的线程同步方法有一个整体的认识,而 ...
随机推荐
- Journal of Proteome Research | 人类牙槽骨蛋白的蛋白质组学和n端分析:改进的蛋白质提取方法和LysargiNase消化策略增加了蛋白质组的覆盖率和缺失蛋白的识别 | (解读人:卜繁宇)
文献名:Proteomic and N-Terminomic TAILS Analyses of Human Alveolar Bone Proteins: Improved Protein Extr ...
- Bisecting GlcNAc is a general suppressor of terminal modification of N-glycan (解读人:王茹凯)
文献名:Bisecting GlcNAc is a general suppressor of terminal modification of N-glycan(平分GlcNAc是N-聚糖末端修饰的 ...
- session生命周期,与cookie的区别
sessinon在用户访问第一次访问服务器时创建. Session什么时候失效? 1. 服务器会把长时间没有活动的Session从服务器内存中清除,此时Session便失效.Tomcat中Sessio ...
- OpenCV-Python OpenCV中的K-Means聚类 | 五十八
目标 了解如何在OpenCV中使用cv.kmeans()函数进行数据聚类 理解参数 输入参数 sample:它应该是np.float32数据类型,并且每个功能都应该放在单个列中. nclusters( ...
- 使用FME平移shapefile文件
- Reface.AppStarter 框架初探
Reface.AppStarter 是一种基于 .NetFramework 的应用程序启动模式,使用该启动模式,你可以轻松的得到以下功能 : IOC / DI 自动注册与装配 简化配置 垂直模块化你的 ...
- Nginx是什么东东?
Nginx的产生 没有听过Nginx?那么一定听过它的"同行"Apache吧!Nginx同Apache一样都是一种WEB服务器.基于REST架构风格,以统一资源描述符(Unifor ...
- unix中数据缓冲区高速缓冲的设计
目录 1. 概述 2. 缓冲区的设计 2.1 缓冲区头部 2.2 缓冲区的结构 2.3 缓冲区的检索算法 2.3. 申请一个缓冲区算法 getblk 2.3.2 释放一个缓冲区算法 brelse 2. ...
- Unity 游戏框架搭建 2019 (十八~二十) 概率函数 & GameObject 显示、隐藏简化 & 第二章 小结与快速复习
在笔者刚做项目的时候,遇到了一个需求.第一个项目是一个跑酷游戏,而跑酷游戏是需要一条一条跑道拼接成的.每个跑道的长度是固定的,而怪物的出现位置也是在跑道上固定好的.那么怪物出现的概率决定一部分关卡的难 ...
- FastDFTJava客户端使用
1.1.java客户端 余庆先生提供了一个Java客户端,但是作为一个C程序员,写的java代码可想而知.而且已经很久不维护了. 这里推荐一个开源的FastDFS客户端,支持最新的SpringBoot ...