当前位置:主页 > 软件编程 > .NET代码 >

c#互斥锁Mutex类用法介绍

时间:2022-07-21 11:09:32 | 栏目:.NET代码 | 点击:

什么是Mutex

“mutex”是术语“互相排斥(mutually exclusive)”的简写形式,也就是互斥量。互斥量跟临界区中提到的Monitor很相似,只有拥有互斥对象的线程才具有访问资源的权限,由于互斥对象只有一个,因此就决定了任何情况下此共享资源都不会同时被多个线程所访问。当前占据资源的线程在任务处理完后应将拥有的互斥对象交出,以便其他线程在获得后得以访问资源。互斥量比临界区复杂,因为使用互斥不仅仅能够在同一应用程序不同线程中实现资源的安全共享,而且可以在不同应用程序的线程之间实现对资源的安全共享。

.Net中mutex由Mutex类来表示。

先绕一小段路

在开始弄明白Mutex如何使用之前,我们要绕一小段路再回来。

读书的时候,大家接触互斥量、信号量这些玩意儿应该是在《操作系统》这一科。所以,其实这些玩意儿出现的原由是作为OS功能而存在。来看看Mutex的声明:

[ComVisibleAttribute(true)]
public sealed class Mutex : WaitHandle

于是我们不得不再走远一些,看看WaitHandel的声明:

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

WaitHandle实现了一个接口,又继承了一个父类。看看它的父类MarshalByRefObject

MarshalByRefObject 类

允许在支持远程处理的应用程序中跨应用程序域边界访问对象。

备注:
应用程序域是一个操作系统进程中一个或多个应用程序所驻留的分区。同一应用程序域中的对象直接通信。不同应用程序域中的对象的通信方式有两种:一种是跨应用程序域边界传输对象副本,一种是使用代理交换消息。

MarshalByRefObject 是通过使用代理交换消息来跨应用程序域边界进行通信的对象的基类。

好啦,剩下的内容不用再看,否则就绕得太远了。我们现在知道Mutex是WaitHandle的子类(偷偷地告诉你,以后要提到的EventWaitHandle、信号量Semaphore也是,而AutoResetEvent和ManualResetEvent则是它的孙子),而WaitHandle又继承自具有在操作系统中跨越应用程序域边界能力的MarshalByRefObject类。所以我们现在可以得到一些结论:

有点象Monitor?不如当它是lock。

好了,终于绕回来了。来看看怎么使用Mutex

由于这两个函数不等效于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还有两个重载的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需要注意的两个细节

题外话:

很奇怪,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()好了~

您可能感兴趣的文章:

相关文章