c# mutex

参考地址:http://blog.sina.com.cn/s/blog_68e4d2910100q6uj.htmlhtml

 

什么是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的声明:app

[ComVisibleAttribute(true)]
public abstract class WaitHandle : MarshalByRefObject, IDisposable
ide

  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.
        mut.ReleaseMutex();
    }
}

  虽然这只是一个示意性的实例,可是我仍然不得不由于这个示例中没有使用try/finally来保证ReleaseMutex的执行而表示对微软的鄙视。对于一个初学的人来讲,第一个看到的例子可能会永远影响这我的使用的习惯,因此是否在简单示意的同时,也能“简单地”给你们show一段足够规范的代码?更况且有至关部分的人都是直接copy sample code……一边告诫全部人Abandoned Mutexes的危害,一边又给出一段一个异常就能够轻易引起这种错误的sample,MSDN不可细看。

  我不得不说Mutex的做用于其说象Monitor不如说象lock,由于它只有等效于Monitro.Enter()/Exit()的做用,不一样之处在于Mutex请求的锁就是它本身。正由于如此,Mutex是能够也是必须(不然哪来的锁?)被实例化的,而不象Monitor是个Static类,不能有本身的实例。

全局和局部的Mutex

  若是在一个应用程序域内使用Mutex,固然不如直接使用Monitor/lock更为合适,由于前面已经提到Mutex须要更大的开销而执行较慢。不过Mutex毕竟不是Monitor/lock,它生来应用的场景就应该是用于进程间同步的。

  除了在上面示例代码中没有参数的构造函数外,Mutex还能够被其它的构造函数所建立:

  • Mutex():用无参数的构造函数获得的Mutex没有任何名称,而进程间没法经过变量的形式共享数据,因此没有名称的Mutex也叫作局部(Local)Mutex。另外,这样建立出的Mutex,建立者对这个实例并无拥有权,仍然须要调用WaitOne()去请求全部权。
  • Mutex(Boolean initiallyOwned):与上面的构造函数同样,它只能建立没有名称的局部Mutex,没法用于进程间的同步。Boolean参数用于指定在建立者建立Mutex后,是否马上得到拥有权,所以Mutex(false)等效于Mutex()。
  • Mutex(Boolean initiallyOwned, String name):在这个构造函数里咱们除了能指定是否在建立后得到初始拥有权外,还能够为这个Mutex取一个名字。只有这种命名的Mutex才能够被其它应用程序域中的程序所使用,所以这种Mutex也叫作全局(Global)Mutex。若是String为null或者空字符串,那么这等同于建立一个未命名的Mutex。由于可能有其余程序先于你建立了同名的Mutex,所以返回的Mutex实例可能只是指向了同名的Mutex而已。可是,这个构造函数并无任何机制告诉咱们这个状况。所以,若是要建立一个命名的Mutex,而且指望知道这个Mutex是否由你建立,最好使用下面两个构造函数中的任意一个。最后,请注意name是大小写敏感的。
  • Mutex(Boolean initiallyOwned, String name, out Boolean createdNew):头两个参数与上面的构造函数相同,第三个out参数用于代表是否得到了初始的拥有权。这个构造函数应该是咱们在实际中使用较多的。
  • Mutex(Boolean initiallyOwned, String name, out Booldan createdNew, MutexSecurity):多出来的这个MutexSecurity参数,也是因为全局Mutex的特性所决定的。由于能够在操做系统范围内被访问,所以它引起了关于访问权的安全问题,好比哪一个Windows帐户运行的程序能够访问这个Mutex,是否能够修改这个Mutext等等。关于Mutex安全性的问题,这里并不打算仔细介绍了,看看这里应该很容易明白。

  另外,Mutex还有两个重载的OpenExisting()方法能够打开已经存在的Mutex。

Mutex的用途

  如前所述,Mutex并不适合于有相互消息通知的同步;另外一方面而咱们也屡次提到局部Mutex应该被Monitor/lock所取代;而跨应用程序的、相互消息通知的同步由将在后面讲到的EventWaiteHandle/AutoResetEvent/ManualResetEvent承担更合适。因此,Mutex在.net中应用的场景彷佛很少。不过,Mutex有个最多见的用途:用于控制一个应用程序只能有一个实例运行。

using System;
using System.Threading;

class MutexSample
{
    private static Mutex mutex = null;  //设为Static成员,是为了在整个程序生命周期内持有Mutex

    static void Main()
    {
        bool firstInstance;
       
        mutex = new Mutex(true, @"Global\MutexSampleApp", out firstInstance);
        try
        {
            if (!firstInstance)
            {
                Console.WriteLine ("已有实例运行,输入回车退出……");
                Console.ReadLine();
                return;
            }
            else
            {
                Console.WriteLine ("咱们是第一个实例!");
                for (int i=60; i > 0; --i)
                {
                    Console.WriteLine (i);
                    Thread.Sleep(1000);
                }
            }
        }
        finally
        {
            //只有第一个实例得到控制权,所以只有在这种状况下才须要ReleaseMutex,不然会引起异常。
            if (firstInstance)
            {
                mutex.ReleaseMutex();
            }
            mutex.Close();
            mutex = null;
        }
    }
}

  这是一个控制台程序,你能够在编译后尝试一次运行多个程序,结果固然老是只有一个程序在倒数计时。你可能会在互联网上找到其它实现应用程序单例的方法,好比利用 Process 查找进程名、利用Win32 API findwindow 查找窗体的方式等等,不过这些方法都不能保证绝对的单例。由于多进程和多线程是同样的,因为CPU时间片随机分配的缘由,可能出现多个进程同时检查到没有其它实例运行的情况。这点在CPU比较繁忙的状况下容易出现,现实的例子好比傲游浏览器。即使你设置了只容许一个实例运行,当系统比较忙的时候,只要你尝试屡次打开浏览器,那就有可能“幸运”的打开若干独立的浏览器窗口。

  别忘了,要实现应用程序的单例,须要在在整个应用程序运行过程当中都保持Mutex,而不仅是在程序初始阶段。因此,例子中Mutex的创建和销毁代码包裹了整个Main()函数。

使用Mutex须要注意的两个细节

  1. 可能你已经注意到了,例子中在给Mutex命名的字符串里给出了一个“Global\”的前缀。这是由于在运行终端服务(或者远程桌面)的服务器上,已命名的全局 mutex 有两种可见性。若是名称之前缀“Global\”开头,则 mutex 在全部终端服务器会话中均为可见。若是名称之前缀“Local\”开头,则 mutex 仅在建立它的终端服务器会话中可见,在这种状况下,服务器上各个其余终端服务器会话中均可以拥有一个名称相同的独立 mutex。若是建立已命名 mutex 时不指定前缀,则它将采用前缀“Local\”。在终端服务器会话中,只是名称前缀不一样的两个 mutex 是独立的 mutex,这两个 mutex 对于终端服务器会话中的全部进程均为可见。即:前缀名称“Global\”和“Local\”仅用来讲明 mutex 名称相对于终端服务器会话(而并不是相对于进程)的范围。最后须要注意“Global\”和“Local\”是大小写敏感的。
  2. 既然父类实现了IDisposalble接口,那么说明这个类必定须要你手工释放那些非托管的资源。因此必须使用try/finally,亦或我讨厌的using,调用Close()方法来释放Mutex所占用的全部资源!

题外话:
  很奇怪,Mutex的父类WaitHandle实现了IDisposable,可是咱们在Mutex上却找不到Dispose()方法,因为这个缘由上面代码的finally中咱们用的是Close()来释放Mutex所占用的资源。其实,这里的Close()就等效于Dispose(),可这是为何?   再去看看WaitHandle,咱们发现它实现的Disopose()方法是protected的,所以咱们没有办法直接调用它。而它公开了一个Close()方法给调用者们用于替代Dispose(),所以Mutex上也就只有Close()。可这又是为何?   话说.Net最初的设计师是微软从Borland公司挖过来的,也就是Delphi之父。熟悉Delphi的人都知道,Object Pascal构架中用于释放资源的方法就是Dispose(),因此Dispose()也成为.Net构架中的重要的一员。   不过从语义上来说,对于文件、网络链接之类的资源“Close”比“Dispose”更符合咱们的习惯。所以“体贴”的微软为了让用户(也就是咱们这些写代码的人)更“舒服”,在这种语义上更适合用Close的资源上,老是提供Close()做为Disopose()的公共实现。其实Close()内部不过是直接调用Dispose()而已。对于这种作法,我在感动之余实在以为有些多余了,到底要把一个东西搞得多么变幻无穷才肯罢休?   若是你实在喜欢Dispose(),那么能够用向上转型 ((IDisposable)((WaitHandle)mutex)).Dispose()把它找出来。即强制把mutex转换为WaitHandle,而后再把WaitHandle强制转型为IDisposable,而IDisposable上的Dispose()是public的。不过咱们终究并不肯定Mutex以及WaitHandle的Close()中究竟是不是在override的时候加入了什么逻辑,因此仍是老老实实用Close()好了~

相关文章
相关标签/搜索