參考地址: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多線程
因而咱們不得再也不走遠一些,看看WaitHandel的聲明:app
[ComVisibleAttribute(true)]
public abstract class WaitHandle : MarshalByRefObject, IDisposableide
WaitHandle實現了一個接口,又繼承了一個父類。IDisposable在C#線程同步(2)- 臨界區&Monitor關於Using的題外話中已簡單提到,這裏就再也不多說了。看看它的父類MarshalByRefObject:函數
MarshalByRefObject 類
容許在支持遠程處理的應用程序中跨應用程序域邊界訪問對象。
……
備註:
應用程序域是一個操做系統進程中一個或多個應用程序所駐留的分區。同一應用程序域中的對象直接通訊。不一樣應用程序域中的對象的通訊方式有兩種:一種是跨應用程序域邊界傳輸對象副本,一種是使用代理交換消息。
MarshalByRefObject 是經過使用代理交換消息來跨應用程序域邊界進行通訊的對象的基類。……
好啦,剩下的內容不用再看,不然就繞得太遠了。咱們如今知道Mutex是WaitHandle的子類(偷偷地告訴你,之後要提到的EventWaitHandle、信號量Semaphore也是,而AutoResetEvent和ManualResetEvent則是它的孫子),而WaitHandle又繼承自具備在操做系統中跨越應用程序域邊界能力的MarshalByRefObject類。因此咱們如今能夠獲得一些結論:
有點象Monitor?不如當它是lock。
好了,終於繞回來了。來看看怎麼使用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還有兩個重載的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的父類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()好了~