本文详细讲解了c# mutex的Mutex类的用法,对大家的学习或工作有一定的参考价值。有需要的朋友下面和边肖一起学习。
什么是Mutex
“互斥量”是术语“互斥量”的简称,即互斥量。互斥非常类似于临界区中提到的监视器。只有互斥的线程才能访问资源。因为只有一个互斥体,所以决定了这个共享资源在任何情况下都不会被多个线程同时访问。当前占用资源的线程应该在任务处理完毕后交出其互斥体,以便其他线程在获得资源后可以访问。互斥比临界区更复杂,因为使用互斥不仅可以实现同一应用程序不同线程间的资源安全共享,还可以实现不同应用程序线程间的资源安全共享。
中的互斥体。Net由mutex类表示。
先绕一小段路。
在我们开始弄清楚如何使用互斥体之前,我们必须绕一小段路,然后再回来。
人在阅读时,要接触主题《操作系统》中的互斥体和信号量。所以,其实这些东西之所以会出现,是因为它们是作为OS功能存在的。看看Mutex的声明:
[ComVisibleAttribute(true)]
公共密封类互斥体:WaitHandle
类上有一个属性:ComVisibleAttribute(true),它指示类成员向COM成员公开。别管它了,只要你知道这个东西和COM有关系,很可能和Windows有关系;互斥体有一个父类:WaitHandle。
所以我们必须更进一步,看看WaitHandel的声明:
[ComVisibleAttribute(true)]
公共抽象类WaitHandle : MarshalByRefObject,IDisposable
WaitHandle实现了一个接口并继承了一个父类。看看它的父类MarshalByRefObject:
MarshalByRefObject 类
允许在支持远程处理的应用程序中跨应用程序域边界访问对象。
备注:
应用程序域是一个分区,其中一个或多个应用程序驻留在操作系统进程中。同一应用程序域中的对象直接通信。不同应用程序域中的对象有两种通信方式:一种是跨应用程序域边界传输对象副本,另一种是通过代理交换消息。
MarshalByRefObject是通过使用代理交换消息来跨应用程序域边界进行通信的对象的基类。
好了,剩下的内容就不看了,不然就太过分了。现在我们知道Mutex是WaitHandle的子类(偷偷告诉你,以后要提到的EventWaitHandle和Semaphore信号量也是它的孙辈,AutoResetEvent和ManualResetEvent是它的孙辈),WaitHandle继承自MarshalByRefObject类,具有跨越操作系统中应用程序域边界的能力。所以我们现在可以得出一些结论:
Mutex是封装Win32 API的一个类,它会直接调用操作系统的“对应”部分。Monitor不从任何父类继承,但相对来说是“本机”的。网本身(当然,Net最终依靠运行时来调用操作系统的各种API)。与Monitor相比,你可以把Mutex想象成一个关于Win32 mutex API的外壳。互斥可以跨应用/应用域,所以可以用于应用域/应用之间的通信和互斥;据我们所知,只有应用程序中的线程可以通信。实际上,如果用于锁定的对象是从MarshalByRefObject派生的,Monitor也可以在多个应用程序域中提供锁定。由于互斥体需要调用操作系统资源,其执行成本远高于Monitor,所以如果只需要同步应用内线程间的操作,Monitor/lock应该是首选。
有点象Monitor?不如当它是lock。
好吧,它终于回来了。让我们看看如何使用互斥。
Waitone ()/waitone (int32,boolean)/waitone (timespan,boolean):请求所有权,这个调用将阻塞,直到当前互斥体收到一个信号,或者直到达到一个可选的超时间隔。这些方法看起来非常类似于Wait()方法及其在Monitor上的重载,只是它们不需要提供一个锁定的对象作为参数。但不要误会,WaitOne()本质上相当于Monitor。Enter()/TryEnter(),而不是Monitor。等等()!这是因为这个WaitOne()没有办法像Monitor一样释放当前的互斥体。获得控制权后等待(),然后阻塞自身。ReleaseMutex():释放当前互斥体一次。注意这里强调一次,因为有互斥的线程可以重复调用Wait系列函数而不妨碍其执行;就像这个监视器的Enter()/Exit()在获取对象锁后可以反复调用一样。调用互斥体的次数由公共语言运行库(CLR)保存,每个WaitOne()计数为1,每个ReleaseMutex()计数为-1。只要这个计数不为0,其他互斥等待者就会认为互斥没有被释放,没有办法得到互斥。另外,就像班长一样。Exit(),只有互斥体的所有者才能使用RleaseMutex(),否则会抛出异常。如果一个线程在拥有互斥体时终止,我们称之为被放弃。在MSDN,微软警告说这是一个“严重的”编程错误。这意味着互斥体的所有者获得所有权后,WaitOne()和RelaseMutex()的次数不相等,调用者不负责任地停止,可能会导致被互斥体保护的资源处于不一致的状态。其实这无非是提醒你记得在try/finally结构中使用互斥。
因为这两个函数不等同于Monitor的Wait()和Pulse(),只有Mutex()和WaitOne()这两个方法不能应用到我们的例子中。
当然,在Mutext上还有其他一些同步通知的方法,但都是其父类WaitHandle上的静态方法。所以它们并不是专门为互斥体“量身定制”的,这与互斥体的使用方式有些格格不入(可以尝试用互斥体代替Monitor来实现我们之前的场景),或者说互斥体其实很不情愿的拥有这些方法。我们将在下一篇关于EventWaitHandle的博客中深入讨论互斥和通知的问题。这里我们暂且放在一边,直接借用MSDN上的例子来简单解释一下互斥最简单的应用场景:
//此示例显示如何使用互斥体来同步访问
//到受保护的资源。与监视器不同,互斥体可以与
//WaitHandle。WaitAll和WaitAny,并且可以被传递
//AppDomain边界。
使用系统;
使用系统。穿线;
分类试验
{
//创建新的互斥体。创建线程不拥有
//互斥。
私有静态互斥mut=new Mutex();
私有常量整数=1;
private const int numThreads=3;
静态空干管()
{
//创建将使用受保护资源的线程。
for(int I=0;i numThreads我)
{
Thread myThread=new Thread(new ThreadStart(MyThreadProc));
流言蜚语。名称=字符串。Format('Thread{0} ',I 1);
流言蜚语。start();
}
//主线程退出,但应用程序继续
//运行,直到所有前台线程都已退出。
}
私有静态void MyThreadProc()
{
for(int I=0;一.登记;我)
{
user resource();
}
}
//此方法表示必须同步的资源
//以便一次只能有一个线程进入。
私有静态void UseResource()
{
//等到安全了再进入。
mut。wait one();
控制台。WriteLine(“{ 0 }已进入受保护区域”,
线程。CurrentThread . Name);
//在此处放置访问不可重入资源的代码。
//模拟一些工作。
线程。睡眠(500);
控制台。WriteLine(“{ 0 }正在离开受保护区域\r\n”,
线程。CurrentThread . Name);
//释放互斥体。
mut。ReleaseMutex
}
}
虽然这只是一个示意性的例子,但我还是要表明我对微软的鄙视,因为这个例子中没有使用try/finally来保证ReleaseMutex的执行。对于一个初学者来说,你看到的第一个例子可能总是会影响这个人的使用习惯,那么有没有可能在简单指明的同时,“简单”给你看一段足够标准的代码呢?更有甚者,相当多的人直接抄袭样本代码.在警告每个人被抛弃的互斥体的危害的同时,他们也给出了一个例子,这个例子很容易由于一个异常而导致这种错误。MSDN不应该被审查。
不得不说Mutex的功能更像是一个锁而不是监视器,因为它只相当于Monitro。Enter()/Exit(),不同的是互斥请求的锁是它自己。正因为如此,互斥体可以也必须是(否则锁在哪里?)是实例化的,不像Monitor是静态类,不能有自己的实例。
全局和局部的Mutex
如果在应用程序域中使用互斥,当然直接使用Monitor/lock更合适,因为前面提到过,互斥需要更多开销,执行起来更慢。但是,互斥体毕竟不是Monitor/lock,它诞生应用的场景应该是用于进程间同步的。
除了上面示例代码中没有参数的构造函数之外,互斥体也可以由其他构造函数创建:
Mutex():无参数构造函数得到的互斥体是没有名字的,数据不能以变量的形式在进程间共享,所以没有名字的互斥体也叫局部)互斥体。另外,以这种方式创建的互斥体的创建者并没有这个实例的所有权,仍然需要调用WaitOne()来请求所有权。互斥体(Boolean initiallyOwned):和上面的构造函数一样,只能创建一个没有名字的本地互斥体,不能用于进程间的同步。布尔参数用于指定创建者在创建互斥体后是否立即获得所有权,因此Mutex(false)等同于Mutex()。Mutex(boolean initial owned,string name):在这个构造函数中,我们可以指定创建后是否获取初始所有权,也可以给这个mutex取一个名字。只有这个已命名的互斥体可以被其他应用程序域中的程序使用,所以这个互斥体也称为全局互斥体。如果String为null或空,则相当于创建一个未命名的互斥体。因为另一个程序可能在你之前创建了一个同名的互斥体,所以返回的互斥体实例可能只是指向一个同名的互斥体。然而,这个构造函数没有任何机制告诉我们这一点。所以,如果你想创建一个已命名的互斥体,想知道这个互斥体是不是你创建的,最好使用下面两个构造函数中的任意一个。最后,请注意名称区分大小写。mutex(boolean initial owned,string name,out boolean created new):前两个参数与上面的构造函数相同,第三个out参数用来表示是否获得初始所有权。在实践中应该更多地使用这个构造函数。mutex(boolean initial owned,string name,out bool Dan created new,MutexSecurity):这个额外的互斥安全参数也是由全局互斥的特性决定的。因为可以在操作系统范围内访问,所以提出了访问权限的安全问题,比如哪个Windows账户程序可以访问这个互斥体,是否可以修改这个互斥体等等。关于互斥的安全性,我不打算在这里详细介绍。看这里应该很容易理解。
另外,Mutex有两个重载的OpenExisting()方法来打开现有的Mutex。
Mutex的用途
如前所述,互斥体不适合与相互消息通知同步;另一方面,我们多次提到,应该用Monitor/lock代替局部互斥;然而,由EventWaiteHandle/AutoresetEvent/Manual ResetEvent来承担跨应用程序的相互消息通知的同步更合适,这将在后面讨论。因此,互斥体应用于的场景似乎并不多。网。然而,互斥体有一个最常见的用途:它用于控制一个应用程序只有一个实例可以运行。
使用系统;
使用系统。穿线;
类别mutex示例
{
私有静态互斥体互斥体=null//设置为静态成员,在整个程序生命周期中持有互斥锁。
静态空干管()
{
bool firstInstance
mutex=new Mutex(true,@'Global\MutexSampleApp ',out first instance);
尝试
{
如果(!第一个实例)
{
控制台。WriteLine('有一个现有实例正在运行,按enter键退出.');
控制台。ReadLine();
返回;
}
其他
{
控制台。WriteLine('我们是第一个实例!');
for(int I=60;I 0;-我)
{
控制台。WriteLine(I);
线程。睡眠(1000);
}
}
}
最后
{
//只有第一个实例获得控制权,所以只有在这种情况下才需要ReleaseMutex,否则会抛出异常。
if(第一个实例)
{
互斥。ReleaseMutex
}
互斥。close();
互斥体=null
}
}
}
这是一个控制台程序。你可以尝试编译后同时运行多个程序。结果当然只有一个程序在倒计时。你可以在网上找到其他实现应用单例的方法,比如用Process查找进程名,用Win32 API findwindow查找窗体等。但是这些方法不能保证绝对的单例。因为多进程和多线程是一样的,由于CPU时间片的随机分配,可能会出现多个进程同时检查没有其他实例在运行的情况。CPU忙的时候容易出现这种情况,比如傲游浏览器。即使你设置只允许一个实例运行,当系统繁忙时,只要你试着打开几次浏览器,就有可能“幸运地”打开几个独立的浏览器窗口。
别忘了,要实现应用程序的单例性,需要在应用程序的整个运行过程中保持互斥,而不仅仅是在程序的初始阶段。因此,示例中互斥体的构建和销毁代码包装了整个Main()函数。
使用Mutex需要注意的两个细节
您可能已经注意到,在这个例子中,在以Mutex命名的字符串中给出了前缀“Global”。这是因为命名的全局互斥体在运行终端服务的服务器(或远程桌面)上有两种可见性。如果名称以前缀“Global”开头,互斥体在所有终端服务器会话中都可见。如果名称以前缀“Local”开头,互斥体只在创建它的终端服务器会话中可见。在这种情况下,服务器上的每个其他终端服务器会话都可以有一个同名的独立互斥体。如果在创建命名互斥体时没有指定前缀,它将使用前缀“Local”。在终端服务器会话中,两个名称前缀不同的互斥体是独立的互斥体,它们对终端服务器会话中的所有进程都是可见的。也就是说,前缀名“Global”和“Local”仅用于说明相对于终端服务器会话(而不是相对于进程)的互斥体名称的范围。最后,应该注意“全局”和“本地”是区分大小写的。由于父类实现了IDisposalble接口,这意味着该类必须要求您手动释放那些非托管资源。所以我们必须使用try/finally,或者我很讨厌的using,来调用Close()方法释放互斥体占用的所有资源!
题外话:
很奇怪,Mutex的父类WaitHandle实现了IDisposable,但是我们在Mutex上找不到Dispose()方法。为此,我们在上述代码的finally中使用Close()来释放互斥体占用的资源。其实这里的Close()相当于Dispose(),但是为什么呢?
再看WaitHandle,发现它的Disopose()方法是受保护的,所以不能直接调用。而且它公开了一个Close()方法供调用者替换Dispose(),所以互斥体上只有Close()。但这是为什么呢?
换句话说。Net是微软从Borland公司挖过来的,Borland公司是Delphi之父。熟悉Delphi的人都知道,Object Pascal框架中用来释放资源的方法是Dispose(),所以Dispose()也成为了。Net框架。
但从语义上来说,对于文件、网络连接等资源,“关闭”比“处置”更符合我们的习惯。所以为了让用户(也就是我们这些写代码的人)更“舒服”,体贴的微软总是在这个语义上更合适的资源上提供Close()作为Disopose()的公共实现。其实Close()只是在内部直接调用Dispose()。对于这种做法,我在感动之余,真的觉得有点多余。在我们放弃之前,我们必须做一件千变万化的事情?
如果你真的喜欢Dispose(),那么你可以用up transformation((idisposable)((wait handle)mutex))找到它。处置()。即互斥体被强制转换为WaitHandle,然后WaitHandle被强制转换为IDisposable,IDisposable上的Dispose()是public。不过毕竟我们也不确定Mutex和WaitHandle的Close()里有没有添加什么逻辑来override,所以还是老老实实用Close()吧~
关于c# mutex的Mutex类用法的这篇文章就到这里了。希望对大家的学习有帮助,也希望大家多多支持。
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。