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 | Utilization of the Proteome Data Deposited in SRMAtlas for Validating the Existence of the Human Missing Proteins in GPM (解读人:梁嘉琪)
文献名:Utilization of the Proteome Data Deposited in SRMAtlas for Validating the Existence of the Human ...
- hdu1253胜利大逃亡(城堡的怪物太狠,主角难免天天逃亡)
题目链接:http://icpc.njust.edu.cn/Problem/Hdu/1253/ 其实就是二维扩展到三维了,增加了搜索方向,其他的没什么不同. 代码如下: #include<bit ...
- 题解 P1002 【过河卒】
正文 简单描述一下题意: 士兵想要过河,他每一次可以往下走一格,也可以往右走一格,但马一步走到的地方是不能走的,问走到\(n\)行,\(m\)列有多少种走法 我们显然应该先根据马的位置将不能走的格子做 ...
- 图-搜索-DFS-51. N皇后
2020-03-15 19:49:59 问题描述: n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击. 上图为 8 皇后问题的一种解法. 给定一个整数 n ...
- 使用IDEA操作Hbase API 报错:org.apache.hadoop.hbase.client.RetriesExhaustedException的解决方法:
使用IDEA操作Hbase API 报错:org.apache.hadoop.hbase.client.RetriesExhaustedException的解决方法: 1.错误详情: Excepti ...
- Python 聊天界面编写
import os from tkinter import * import time os.chdir('E:\\actdata') def main(): def sendMsg():#发送消息 ...
- WeChat-SmallProgram:自定义select下拉选项框组件
1):创建组件所需的文件 2):自定义组件 CSS 及 JS 组件的wxml: <view class='com-selectBox'> <view class='com-sCont ...
- coding++:js实现基于Base64的编码及解码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- IOS 获取系统时间戳
IOS 获取系统时间戳常用方法 通用方法有如下三种: NSLog(); NSDate* dat = [NSDate dateWithTimeIntervalSinceNow:]; NSTimeInte ...
- Python——NumPy库入门
1.数据的纬度 维度:一组数据的组织形式 1.1 一维数据 一维数据由对等关系的有序或无序数据构成,采用线性方式组织 ,对应列表.数组和集合等概念 列表:数据类型可以不同 ,如 3.1413, 'pi ...