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()好了~

相關文章
相關標籤/搜索