什么是Mutex “mutex”是术语“互相排斥(mutually exclusive)”的简写形式,也就是互斥量。互斥量跟临界区中提到的Monitor很相似,只有拥有互斥对象的线程才具有访问资源的权限,由于互斥对象只有一个,因此就决定了任何情况下此共享资源都不会同时被多个线程所访问。当前占据资源的线程在任务处理完后应将拥有的互斥对象交出,以便其他线程在获得后得以访问资源。互斥量比临界区复杂,因为使用互斥不仅仅能够在同一应用程序不同线程中实现资源的安全共享,而且可以在不同应用程序的线程之间实现对资源的安全共享。.Net中mutex由Mutex类来表示。 先绕一小段路 在开始弄明白Mutex如何使用之前,我们要绕一小段路再回来。 读书的时候,大家接触互斥量、信号量这些玩意儿应该是在《操作系统》这一科。所以,其实这些玩意儿出现的原由是作为OS功能而存在。来看看Mutex的声明: [ComVisibleAttribute(true)] public sealed class Mutex : WaitHandle - 类上有个属性:ComVisibleAttribute(true),表明该类成员对COM成员公开。不去管它,只要知道这玩意儿跟COM有关系了,那大概跟Windows关系比较密了;
- Mutex它有个父类:WaitHandle
于是我们不得不再走远一些,看看WaitHandel的声明: [ComVisibleAttribute(true)] public abstract class WaitHandle : MarshalByRefObject, IDisposable WaitHandle实现了一个接口,又继承了一个父类。IDisposable在C#线程同步(2)- 临界区&Monitor关于Using的题外话中已简单提到,这里就不再多说了。看看它的父类MarshalByRefObject: MarshalByRefObject 类 允许在支持远程处理的应用程序中跨应用程序域边界访问对象。 …… 备注: 应用程序域是一个操作系统进程中一个或多个应用程序所驻留的分区。同一应用程序域中的对象直接通信。不同应用程序域中的对象的通信方式有两种:一种是跨应用程序域边界传输对象副本,一种是使用代理交换消息。 MarshalByRefObject 是通过使用代理交换消息来跨应用程序域边界进行通信的对象的基类。…… 好啦,剩下的内容不用再看,否则就绕得太远了。我们现在知道Mutex是WaitHandle的子类(偷偷地告诉你,以后要提到的EventWaitHandle、信号量Semaphore也是,而AutoResetEvent和ManualResetEvent则是它的孙子),而WaitHandle又继承自具有在操作系统中跨越应用程序域边界能力的MarshalByRefObject类。所以我们现在可以得到一些结论: - Mutex是封装了Win32 API的类,它将比较直接地调用操作系统“对应”部分功能;而Monitor并没有继承自任何父类,相对来说是.Net自己“原生”的(当然.Net最终还是要靠运行时调用操作系统的各种API)。相较于Monitor,你可以把Mutex近似看作是一个关于Win32互斥量API的壳子。
- Mutex是可以跨应用程序/应用程序域,因此可以被用于应用程序域/应用程序间的通信和互斥;Monitor就我们到目前为止所见,只能在应用程序内部的线程之间通信。其实,如果用于锁的对象派生自MarshalByRefObject,Monitor 也可在多个应用程序域中提供锁定。
- Mutex由于需要调用操作系统资源,因此执行的开销比Monitor大得多,所以如果仅仅需要在应用程序内部的线程间同步操作,Monitor/lock应当是首选。
有点象Monitor?不如当它是lock。 好了,终于绕回来了。来看看怎么使用Mutex。 - WaitOne() / WaitOne(Int32, Boolean) / WaitOne(TimeSpan, Boolean):请求所有权,该调用会一直阻塞到当前 mutex 收到信号,或直至达到可选的超时间隔。这几个方法除了不需要提供锁定对象作为参数外,看起来与Monitor上的Wait()方法及其重载很相似相似。不过千万不要误会,WaitOne()本质上跟Monitor.Enter()/TryEnter()等效,而不是Monitor.Wait()!这是因为这个WaitOne()并没有办法在获取控制权以后象Monitor.Wait()释放当前Mutex,然后阻塞自己。
- ReleaseMutex():释放当前 Mutex 一次。注意,这里强调了一次,因为拥有互斥体的线程可以在重复的调用Wait系列函数而不会阻止其执行;这个跟Monitor的Enter()/Exit()可以在获取对象锁后可以被重复调用一样。Mutex被调用的次数由公共语言运行库(CLR)保存,每WaitOne()一次计数+1,每ReleaseMutex()一次计数-1,只要这个计数不为0,其它Mutex的等待者就会认为这个Mutex没有被释放,也就没有办法获得该Mutex。 另外,跟Monitor.Exit()一样,只有Mutex的拥有者才能RleaseMutex(),否则会引发异常。
- 如果线程在拥有互斥体时终止,我们称此互斥体被遗弃(Abandoned)。在MSDN里,微软以警告的方式指出这属于“严重的”编程错误。这是说拥有mutex的拥有者在获得所有权后,WaitOne()和RelaseMutex()的次数不对等,调用者自身又不负责任地中止,造成mutex 正在保护的资源可能会处于不一致的状态。其实,这无非就是提醒你记得在try/finally结构中使用Mutex。
回想我们在《C#线程同步(2)- 临界区&Monitor》中提到的关于生产者和消费者的场景,由于这两个函数不等效于Monitor的Wait()和Pulse(),所以仅靠这ReleaseMutex()和WaitOne()两个方法Mutex还无法适用于我们那个例子。 当然Mutext上还“算有”其它一些用于同步通知的方法,但它们都是其父类WaitHandle上的静态方法。因此它们并不是为Mutex特意“度身订做”的,与Mutex使用的方式有些不搭调(你可以尝试下用Mutex替换Monitor实现我们之前的场景看看),或者说Mutex其实是有些不情愿的拥有这些方法。我们会在下一篇关于EventWaitHandle的Blog中再深入一些地讨论Mutex和通知的问题。这里暂且让我们放一放,直接借用MSDN上的示例来简单说明Mutex的最简单的应用场景吧: // This example shows how a Mutex is used to synchronize access // to a protected resource. Unlike Monitor, Mutex can be used with // WaitHandle.WaitAll and WaitAny, and can be passed across // AppDomain boundaries.
using System; using System.Threading;
class Test { // Create a new Mutex. The creating thread does not own the // Mutex. private static Mutex mut = new Mutex(); private const int numIterations = 1; private const int numThreads = 3;
static void Main() { // Create the threads that will use the protected resource. for(int i = 0; i < numThreads; i++) { Thread myThread = new Thread(new ThreadStart(MyThreadProc)); myThread.Name = String.Format("Thread{0}", i + 1); myThread.Start(); }
// The main thread exits, but the application continues to // run until all foreground threads have exited. }
private static void MyThreadProc() { for(int i = 0; i < numIterations; i++) { UseResource(); } }
// This method represents a resource that must be synchronized // so that only one thread at a time can enter. private static void UseResource() { // Wait until it is safe to enter. mut.WaitOne();
Console.WriteLine("{0} has entered the protected area", Thread.CurrentThread.Name);
// Place code to access non-reentrant resources here.
// Simulate some work. Thread.Sleep(500);
Console.WriteLine("{0} is leaving the protected area\r\n", Thread.CurrentThread.Name); // Release the Mutex. |