您好,登錄后才能下訂單哦!
本篇內容主要講解“c#的互斥鎖Mutex類怎么使用”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“c#的互斥鎖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的聲明:
[ComVisibleAttribute(true)] public abstract class WaitHandle : MarshalByRefObject, IDisposable
WaitHandle實現了一個接口,又繼承了一個父類。看看它的父類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應當是首選。
好了,終于繞回來了。來看看怎么使用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
。
由于這兩個函數不等效于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,當然不如直接使用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應該被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命名的字符串里給出了一個“Global\”的前綴。這是因為在運行終端服務(或者遠程桌面)的服務器上,已命名的全局 mutex 有兩種可見性。如果名稱以前綴“Global\”開頭,則 mutex 在所有終端服務器會話中均為可見。如果名稱以前綴“Local\”開頭,則 mutex 僅在創建它的終端服務器會話中可見,在這種情況下,服務器上各個其他終端服務器會話中都可以擁有一個名稱相同的獨立 mutex。如果創建已命名 mutex 時不指定前綴,則它將采用前綴“Local\”。在終端服務器會話中,只是名稱前綴不同的兩個 mutex 是獨立的 mutex,這兩個 mutex 對于終端服務器會話中的所有進程均為可見。即:前綴名稱“Global\”和“Local\”僅用來說明 mutex 名稱相對于終端服務器會話(而并非相對于進程)的范圍。最后需要注意“Global\”和“Local\”是大小寫敏感的。
既然父類實現了IDisposalble接口,那么說明這個類一定需要你手工釋放那些非托管的資源。所以必須使用try/finally,亦或我討厭的using,調用Close()方法來釋放Mutex所占用的所有資源!
到此,相信大家對“c#的互斥鎖Mutex類怎么使用”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。