c#互斥鎖Mutex類用法介紹
什么是Mutex
“mutex”是術(shù)語“互相排斥(mutually exclusive)”的簡寫形式,也就是互斥量。互斥量跟臨界區(qū)中提到的Monitor很相似,只有擁有互斥對(duì)象的線程才具有訪問資源的權(quán)限,由于互斥對(duì)象只有一個(gè),因此就決定了任何情況下此共享資源都不會(huì)同時(shí)被多個(gè)線程所訪問。當(dāng)前占據(jù)資源的線程在任務(wù)處理完后應(yīng)將擁有的互斥對(duì)象交出,以便其他線程在獲得后得以訪問資源。互斥量比臨界區(qū)復(fù)雜,因?yàn)槭褂没コ獠粌H僅能夠在同一應(yīng)用程序不同線程中實(shí)現(xiàn)資源的安全共享,而且可以在不同應(yīng)用程序的線程之間實(shí)現(xiàn)對(duì)資源的安全共享。
.Net中mutex由Mutex類
來表示。
先繞一小段路
在開始弄明白Mutex如何使用之前,我們要繞一小段路再回來。
讀書的時(shí)候,大家接觸互斥量、信號(hào)量這些玩意兒應(yīng)該是在《操作系統(tǒng)》這一科。所以,其實(shí)這些玩意兒出現(xiàn)的原由是作為OS功能而存在。來看看Mutex的聲明:
[ComVisibleAttribute(true)] public sealed class Mutex : WaitHandle
- 類上有個(gè)屬性:ComVisibleAttribute(true),表明該類成員對(duì)COM成員公開。不去管它,只要知道這玩意兒跟COM有關(guān)系了,那大概跟Windows關(guān)系比較密了;
- Mutex它有個(gè)父類:WaitHandle
于是我們不得不再走遠(yuǎn)一些,看看WaitHandel的聲明:
[ComVisibleAttribute(true)] public abstract class WaitHandle : MarshalByRefObject, IDisposable
WaitHandle實(shí)現(xiàn)了一個(gè)接口,又繼承了一個(gè)父類??纯此母割?a rel="external nofollow" target="_blank">MarshalByRefObject:
MarshalByRefObject 類
允許在支持遠(yuǎn)程處理的應(yīng)用程序中跨應(yīng)用程序域邊界訪問對(duì)象。
備注:
應(yīng)用程序域是一個(gè)操作系統(tǒng)進(jìn)程中一個(gè)或多個(gè)應(yīng)用程序所駐留的分區(qū)。同一應(yīng)用程序域中的對(duì)象直接通信。不同應(yīng)用程序域中的對(duì)象的通信方式有兩種:一種是跨應(yīng)用程序域邊界傳輸對(duì)象副本,一種是使用代理交換消息。
MarshalByRefObject 是通過使用代理交換消息來跨應(yīng)用程序域邊界進(jìn)行通信的對(duì)象的基類。
好啦,剩下的內(nèi)容不用再看,否則就繞得太遠(yuǎn)了。我們現(xiàn)在知道Mutex是WaitHandle的子類(偷偷地告訴你,以后要提到的EventWaitHandle、信號(hào)量Semaphore也是,而AutoResetEvent和ManualResetEvent則是它的孫子),而WaitHandle又繼承自具有在操作系統(tǒng)中跨越應(yīng)用程序域邊界能力的MarshalByRefObject類。所以我們現(xiàn)在可以得到一些結(jié)論:
- Mutex是封裝了Win32 API的類,它將比較直接地調(diào)用操作系統(tǒng)“對(duì)應(yīng)”部分功能;而Monitor并沒有繼承自任何父類,相對(duì)來說是.Net自己“原生”的(當(dāng)然.Net最終還是要靠運(yùn)行時(shí)調(diào)用操作系統(tǒng)的各種API)。相較于Monitor,你可以把Mutex近似看作是一個(gè)關(guān)于Win32互斥量API的殼子。
- Mutex是可以跨應(yīng)用程序/應(yīng)用程序域,因此可以被用于應(yīng)用程序域/應(yīng)用程序間的通信和互斥;Monitor就我們到目前為止所見,只能在應(yīng)用程序內(nèi)部的線程之間通信。其實(shí),如果用于鎖的對(duì)象派生自MarshalByRefObject,Monitor 也可在多個(gè)應(yīng)用程序域中提供鎖定。
- Mutex由于需要調(diào)用操作系統(tǒng)資源,因此執(zhí)行的開銷比Monitor大得多,所以如果僅僅需要在應(yīng)用程序內(nèi)部的線程間同步操作,Monitor/lock應(yīng)當(dāng)是首選。
有點(diǎn)象Monitor?不如當(dāng)它是lock。
好了,終于繞回來了。來看看怎么使用Mutex。
- WaitOne() / WaitOne(Int32, Boolean) / WaitOne(TimeSpan, Boolean):請求所有權(quán),該調(diào)用會(huì)一直阻塞到當(dāng)前 mutex 收到信號(hào),或直至達(dá)到可選的超時(shí)間隔。這幾個(gè)方法除了不需要提供鎖定對(duì)象作為參數(shù)外,看起來與Monitor上的Wait()方法及其重載很相似相似。不過千萬不要誤會(huì),WaitOne()本質(zhì)上跟Monitor.Enter()/TryEnter()等效,而不是Monitor.Wait()!這是因?yàn)檫@個(gè)WaitOne()并沒有辦法在獲取控制權(quán)以后象Monitor.Wait()釋放當(dāng)前Mutex,然后阻塞自己。
- ReleaseMutex():釋放當(dāng)前 Mutex 一次。注意,這里強(qiáng)調(diào)了一次,因?yàn)閾碛谢コ怏w的線程可以在重復(fù)的調(diào)用Wait系列函數(shù)而不會(huì)阻止其執(zhí)行;這個(gè)跟Monitor的Enter()/Exit()可以在獲取對(duì)象鎖后可以被重復(fù)調(diào)用一樣。Mutex被調(diào)用的次數(shù)由公共語言運(yùn)行庫(CLR)保存,每WaitOne()一次計(jì)數(shù)+1,每ReleaseMutex()一次計(jì)數(shù)-1,只要這個(gè)計(jì)數(shù)不為0,其它Mutex的等待者就會(huì)認(rèn)為這個(gè)Mutex沒有被釋放,也就沒有辦法獲得該Mutex。 另外,跟Monitor.Exit()一樣,只有Mutex的擁有者才能RleaseMutex(),否則會(huì)引發(fā)異常。
- 如果線程在擁有互斥體時(shí)終止,我們稱此互斥體被遺棄(Abandoned)。在MSDN里,微軟以警告的方式指出這屬于“嚴(yán)重的”編程錯(cuò)誤。這是說擁有mutex的擁有者在獲得所有權(quán)后,WaitOne()和RelaseMutex()的次數(shù)不對(duì)等,調(diào)用者自身又不負(fù)責(zé)任地中止,造成mutex 正在保護(hù)的資源可能會(huì)處于不一致的狀態(tài)。其實(shí),這無非就是提醒你
記得在try/finally結(jié)構(gòu)中使用Mutex
。
由于這兩個(gè)函數(shù)不等效于Monitor的Wait()和Pulse(),所以僅靠這ReleaseMutex()和WaitOne()兩個(gè)方法Mutex還無法適用于我們那個(gè)例子。
當(dāng)然Mutext上還“算有”其它一些用于同步通知的方法,但它們都是其父類WaitHandle上的靜態(tài)方法。因此它們并不是為Mutex特意“度身訂做”的,與Mutex使用的方式有些不搭調(diào)(你可以嘗試下用Mutex替換Monitor實(shí)現(xiàn)我們之前的場景看看),或者說Mutex其實(shí)是有些不情愿的擁有這些方法。我們會(huì)在下一篇關(guān)于EventWaitHandle的Blog中再深入一些地討論Mutex和通知的問題。這里暫且讓我們放一放,直接借用MSDN上的示例來簡單說明Mutex的最簡單的應(yīng)用場景吧:
// 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(); } }
雖然這只是一個(gè)示意性的實(shí)例,但是我仍然不得不因?yàn)檫@個(gè)示例中沒有使用try/finally來保證ReleaseMutex的執(zhí)行而表示對(duì)微軟的鄙視。對(duì)于一個(gè)初學(xué)的人來說,第一個(gè)看到的例子可能會(huì)永遠(yuǎn)影響這個(gè)人使用的習(xí)慣,所以是否在簡單示意的同時(shí),也能“簡單地”給大家show一段足夠規(guī)范的代碼?更何況有相當(dāng)部分的人都是直接copy sample code……一邊告誡所有人Abandoned Mutexes的危害,一邊又給出一段一個(gè)異常就可以輕易引發(fā)這種錯(cuò)誤的sample,MSDN不可細(xì)看。
我不得不說Mutex的作用于其說象Monitor不如說象lock,因?yàn)樗挥械刃в贛onitro.Enter()/Exit()的作用,不同之處在于Mutex請求的鎖就是它自己。正因?yàn)槿绱?,Mutex是可以也是必須(否則哪來的鎖?)被實(shí)例化的,而不象Monitor是個(gè)Static類,不能有自己的實(shí)例。
全局和局部的Mutex
如果在一個(gè)應(yīng)用程序域內(nèi)使用Mutex,當(dāng)然不如直接使用Monitor/lock更為合適,因?yàn)榍懊嬉呀?jīng)提到Mutex需要更大的開銷而執(zhí)行較慢。不過Mutex畢竟不是Monitor/lock,它生來應(yīng)用的場景就應(yīng)該是用于進(jìn)程間同步的。
除了在上面示例代碼中沒有參數(shù)的構(gòu)造函數(shù)外,Mutex還可以被其它的構(gòu)造函數(shù)所創(chuàng)建:
- Mutex():用無參數(shù)的構(gòu)造函數(shù)得到的Mutex沒有任何名稱,而進(jìn)程間無法通過變量的形式共享數(shù)據(jù),所以沒有名稱的Mutex也叫做
局部(Local)Mutex
。另外,這樣創(chuàng)建出的Mutex,創(chuàng)建者對(duì)這個(gè)實(shí)例并沒有擁有權(quán),仍然需要調(diào)用WaitOne()去請求所有權(quán)。 - Mutex(Boolean initiallyOwned):與上面的構(gòu)造函數(shù)一樣,它只能創(chuàng)建沒有名稱的局部Mutex,無法用于進(jìn)程間的同步。Boolean參數(shù)用于指定在創(chuàng)建者創(chuàng)建Mutex后,是否立刻獲得擁有權(quán),因此Mutex(false)等效于Mutex()。
- Mutex(Boolean initiallyOwned, String name):在這個(gè)構(gòu)造函數(shù)里我們除了能指定是否在創(chuàng)建后獲得初始擁有權(quán)外,還可以為這個(gè)Mutex取一個(gè)名字。只有這種命名的Mutex才可以被其它應(yīng)用程序域中的程序所使用,因此這種Mutex也叫做
全局(Global)Mutex
。如果String為null或者空字符串,那么這等同于創(chuàng)建一個(gè)未命名的Mutex。因?yàn)榭赡苡衅渌绦蛳扔谀銊?chuàng)建了同名的Mutex,因此返回的Mutex實(shí)例可能只是指向了同名的Mutex而已。但是,這個(gè)構(gòu)造函數(shù)并沒有任何機(jī)制告訴我們這個(gè)情況。因此,如果要?jiǎng)?chuàng)建一個(gè)命名的Mutex,并且期望知道這個(gè)Mutex是否由你創(chuàng)建,最好使用下面兩個(gè)構(gòu)造函數(shù)中的任意一個(gè)。最后,請注意name是大小寫敏感的
。 - Mutex(Boolean initiallyOwned, String name, out Boolean createdNew):頭兩個(gè)參數(shù)與上面的構(gòu)造函數(shù)相同,第三個(gè)out參數(shù)用于表明是否獲得了初始的擁有權(quán)。這個(gè)構(gòu)造函數(shù)應(yīng)該是我們在實(shí)際中使用較多的。
- Mutex(Boolean initiallyOwned, String name, out Booldan createdNew, MutexSecurity):多出來的這個(gè)MutexSecurity參數(shù),也是由于全局Mutex的特性所決定的。因?yàn)榭梢栽诓僮飨到y(tǒng)范圍內(nèi)被訪問,因此它引發(fā)了關(guān)于訪問權(quán)的安全問題,比如哪個(gè)Windows賬戶運(yùn)行的程序可以訪問這個(gè)Mutex,是否可以修改這個(gè)Mutext等等。關(guān)于Mutex安全性的問題,這里并不打算仔細(xì)介紹了,看看這里應(yīng)該很容易明白。
另外,Mutex還有兩個(gè)重載的OpenExisting()方法可以打開已經(jīng)存在的Mutex。
Mutex的用途
如前所述,Mutex并不適合于有相互消息通知的同步;另一方面而我們也多次提到局部Mutex應(yīng)該被Monitor/lock所取代;而跨應(yīng)用程序的、相互消息通知的同步由將在后面講到的EventWaiteHandle/AutoResetEvent/ManualResetEvent承擔(dān)更合適。所以,Mutex在.net中應(yīng)用的場景似乎不多。不過,Mutex有個(gè)最常見的用途:用于控制一個(gè)應(yīng)用程序只能有一個(gè)實(shí)例運(yùn)行。
using System; using System.Threading; class MutexSample { private static Mutex mutex = null; //設(shè)為Static成員,是為了在整個(gè)程序生命周期內(nèi)持有Mutex static void Main() { bool firstInstance; mutex = new Mutex(true, @"Global\MutexSampleApp", out firstInstance); try { if (!firstInstance) { Console.WriteLine ("已有實(shí)例運(yùn)行,輸入回車退出……"); Console.ReadLine(); return; } else { Console.WriteLine ("我們是第一個(gè)實(shí)例!"); for (int i=60; i > 0; --i) { Console.WriteLine (i); Thread.Sleep(1000); } } } finally { //只有第一個(gè)實(shí)例獲得控制權(quán),因此只有在這種情況下才需要ReleaseMutex,否則會(huì)引發(fā)異常。 if (firstInstance) { mutex.ReleaseMutex(); } mutex.Close(); mutex = null; } } }
這是一個(gè)控制臺(tái)程序,你可以在編譯后嘗試一次運(yùn)行多個(gè)程序,結(jié)果當(dāng)然總是只有一個(gè)程序在倒數(shù)計(jì)時(shí)。你可能會(huì)在互聯(lián)網(wǎng)上找到其它實(shí)現(xiàn)應(yīng)用程序單例的方法,比如利用 Process 查找進(jìn)程名、利用Win32 API findwindow 查找窗體的方式等等,不過這些方法都不能保證絕對(duì)的單例。因?yàn)槎噙M(jìn)程和多線程是一樣的,由于CPU時(shí)間片隨機(jī)分配的原因,可能出現(xiàn)多個(gè)進(jìn)程同時(shí)檢查到?jīng)]有其它實(shí)例運(yùn)行的狀況。這點(diǎn)在CPU比較繁忙的情況下容易出現(xiàn),現(xiàn)實(shí)的例子比如傲游瀏覽器。即便你設(shè)置了只允許一個(gè)實(shí)例運(yùn)行,當(dāng)系統(tǒng)比較忙的時(shí)候,只要你嘗試多次打開瀏覽器,那就有可能“幸運(yùn)”的打開若干獨(dú)立的瀏覽器窗口。
別忘了,要實(shí)現(xiàn)應(yīng)用程序的單例,需要在在整個(gè)應(yīng)用程序運(yùn)行過程中都保持Mutex,而不只是在程序初始階段。所以,例子中Mutex的建立和銷毀代碼包裹了整個(gè)Main()函數(shù)。
使用Mutex需要注意的兩個(gè)細(xì)節(jié)
- 可能你已經(jīng)注意到了,例子中在給Mutex命名的字符串里給出了一個(gè)“Global\”的前綴。這是因?yàn)樵谶\(yùn)行終端服務(wù)(或者遠(yuǎn)程桌面)的服務(wù)器上,已命名的全局 mutex 有兩種可見性。如果名稱以前綴“Global\”開頭,則 mutex 在所有終端服務(wù)器會(huì)話中均為可見。如果名稱以前綴“Local\”開頭,則 mutex 僅在創(chuàng)建它的終端服務(wù)器會(huì)話中可見,在這種情況下,服務(wù)器上各個(gè)其他終端服務(wù)器會(huì)話中都可以擁有一個(gè)名稱相同的獨(dú)立 mutex。如果創(chuàng)建已命名 mutex 時(shí)不指定前綴,則它將采用前綴“Local\”。在終端服務(wù)器會(huì)話中,只是名稱前綴不同的兩個(gè) mutex 是獨(dú)立的 mutex,這兩個(gè) mutex 對(duì)于終端服務(wù)器會(huì)話中的所有進(jìn)程均為可見。即:前綴名稱“Global\”和“Local\”僅用來說明 mutex 名稱相對(duì)于終端服務(wù)器會(huì)話(而并非相對(duì)于進(jìn)程)的范圍。最后需要注意“Global\”和“Local\”是大小寫敏感的。
- 既然父類實(shí)現(xiàn)了IDisposalble接口,那么說明這個(gè)類一定需要你手工釋放那些非托管的資源。所以必須使用try/finally,亦或我討厭的using,調(diào)用Close()方法來釋放Mutex所占用的所有資源!
題外話:
很奇怪,Mutex的父類WaitHandle實(shí)現(xiàn)了IDisposable,但是我們在Mutex上卻找不到Dispose()方法,由于這個(gè)原因上面代碼的finally中我們用的是Close()來釋放Mutex所占用的資源。其實(shí),這里的Close()就等效于Dispose(),可這是為什么?
再去看看WaitHandle,我們發(fā)現(xiàn)它實(shí)現(xiàn)的Disopose()方法是protected的,因此我們沒有辦法直接調(diào)用它。而它公開了一個(gè)Close()方法給調(diào)用者們用于替代Dispose(),因此Mutex上也就只有Close()??蛇@又是為什么?
話說.Net最初的設(shè)計(jì)師是微軟從Borland公司挖過來的,也就是Delphi之父。熟悉Delphi的人都知道,Object Pascal構(gòu)架中用于釋放資源的方法就是Dispose(),所以Dispose()也成為.Net構(gòu)架中的重要的一員。
不過從語義上來講,對(duì)于文件、網(wǎng)絡(luò)連接之類的資源“Close”比“Dispose”更符合我們的習(xí)慣。因此“體貼”的微軟為了讓用戶(也就是我們這些寫代碼的人)更“舒服”,在這種語義上更適合用Close的資源上,總是提供Close()作為Disopose()的公共實(shí)現(xiàn)。其實(shí)Close()內(nèi)部不過是直接調(diào)用Dispose()而已。對(duì)于這種做法,我在感動(dòng)之余實(shí)在覺得有些多余了,到底要把一個(gè)東西搞得多么千變?nèi)f化才肯罷休?
如果你實(shí)在喜歡Dispose(),那么可以用向上轉(zhuǎn)型 ((IDisposable)((WaitHandle)mutex)).Dispose()把它找出來。即強(qiáng)制把mutex轉(zhuǎn)換為WaitHandle,然后再把WaitHandle強(qiáng)制轉(zhuǎn)型為IDisposable,而IDisposable上的Dispose()是public的。不過我們終究并不確定Mutex以及WaitHandle的Close()中到底是不是在override的時(shí)候加入了什么邏輯,所以還是老老實(shí)實(shí)用Close()好了~
到此這篇關(guān)于c#互斥鎖Mutex類用法介紹的文章就介紹到這了。希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
c#中object、var和dynamic的區(qū)別小結(jié)
這篇文章主要給大家介紹了關(guān)于c#中object、var和dynamic的區(qū)別,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09C#正則表達(dá)式獲取下拉菜單(select)的相關(guān)屬性值
這篇文章主要介紹了C#正則表達(dá)式獲取下拉菜單(select)的相關(guān)屬性值,比如可以獲得name屬性的值、value值、指定值,需要的朋友可以參考下2014-07-07C#實(shí)現(xiàn)獲取設(shè)置IP地址小工具
c# 開發(fā),方便更改IP地址。由于公司和家里的ip設(shè)置不一樣,公司要求手動(dòng)設(shè)置,在家可以自動(dòng)獲取IP,切都是無線網(wǎng)絡(luò),為了方便操作,故做了這個(gè)小工具!2015-06-06C#使用Region對(duì)圖形區(qū)域構(gòu)造和填充的方法
這篇文章主要介紹了C#使用Region對(duì)圖形區(qū)域構(gòu)造和填充的方法,實(shí)例分析了Region類圖形操作的相關(guān)技巧,需要的朋友可以參考下2015-06-06C#手動(dòng)操作DataGridView使用各種數(shù)據(jù)源填充表格實(shí)例
本文主要介紹了C#手動(dòng)操作DataGridView使用各種數(shù)據(jù)源填充表格實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02C# NAudio 庫的各種常見使用方式之播放 錄制 轉(zhuǎn)碼 音頻可視化
這篇文章主要介紹了C# NAudio 庫的各種常見使用方式之播放 錄制 轉(zhuǎn)碼 音頻可視化,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-05-05