.Net CLR 中的同步机制(一): 互斥体
隨著軟硬件技術(shù)的發(fā)展,無論是在Web服務(wù)或者云計(jì)算,還是單一的應(yīng)用程序,串行方式編寫的軟件越來越少,我們總是可以看見并行的存在。但是并行并不是適合于每一種場景,也完全不是將工作扔到線程池中排隊(duì)運(yùn)行那么簡單。
由于在進(jìn)程中,多個(gè)線程可能需要訪問相同的虛擬內(nèi)存地址空間,如果不進(jìn)行控制就很容易出現(xiàn)數(shù)據(jù)競爭的并發(fā)問題,大多是因?yàn)椴僮鞣窃有院途€程時(shí)間片的原因引起的,導(dǎo)致的現(xiàn)象會(huì)是拋出異常,程序崩潰,數(shù)據(jù)的值和期望不一致,數(shù)據(jù)破壞等等,關(guān)鍵有的時(shí)候還會(huì)隨機(jī)出現(xiàn)一些問題,這次運(yùn)行正確,下一次就不正確了,這些問題都不是簡單的單元測試就可以測試出來的。為了解決這些問題,windows就提供了同步機(jī)制,同步就是唯一能夠保證讓多線程能正確的使用共享的可變狀態(tài)的技術(shù)。
同步一般分為兩種:數(shù)據(jù)同步和控制同步。
數(shù)據(jù)同步:一般是指同步的訪問某個(gè)共享資源主要是內(nèi)存數(shù)據(jù),多個(gè)程序以并行的方式使用相同的資源時(shí)不會(huì)產(chǎn)生干擾。常見的有:lock,Mutex,Monitor,Semaphore等等
控制同步:多線程的運(yùn)行依賴于程序的控制流,一個(gè)線程的運(yùn)行往往要等待其他線程運(yùn)行到某個(gè)點(diǎn)的通知。如:Event等。
這兩種類型我們常常在開發(fā)中結(jié)合使用。
Windows有很多只能由內(nèi)核訪問的內(nèi)核對象,如線程對象,文件對象,當(dāng)然還有我們要介紹的用于同步控制的內(nèi)核對象:互斥體(Mutex),信號(hào)量(Semaphore),事件(Auto-Reset Event, Manual-Reset Event)等。而內(nèi)核對象是在內(nèi)核內(nèi)存中分配的,因此只有在內(nèi)核態(tài)運(yùn)行的代碼中才能訪問到它們,訪問他們需要使用windows api,從而需要內(nèi)核切換,所以使用內(nèi)核對象會(huì)比使用其他的原語需要更高的開銷。但是內(nèi)核對象的優(yōu)勢也非常明顯,很多都是用戶態(tài)的同步機(jī)制(Win32臨界區(qū)或CLR的Monitor等)所無法實(shí)現(xiàn)的,比如進(jìn)程間的同步,對于同步的控制,和執(zhí)行粒度更低的等待,以及托管代碼與非托管代碼之間的互操作。而可以簡單快速可靠使用的api也是我們常常使用的一個(gè)關(guān)鍵。
?
接下來我們先來說一下互斥體。
對于一般性的數(shù)據(jù)競爭問題,解決方法之一就是使用互斥體來使共享狀態(tài)的并發(fā)訪問串行化。互斥體其實(shí)就是構(gòu)建了一段指令臨界域,在這個(gè)臨界域中,同時(shí)只能有一個(gè)線程來執(zhí)行臨界域中的指令。CLR中的互斥體實(shí)現(xiàn)就是Mutex,它的目的就是構(gòu)建擁有互斥行為的臨界域,保證只有一個(gè)線程可以進(jìn)入這個(gè)臨界域。而在底層基本都是使用原子的比較交換(CAS)來實(shí)現(xiàn),這需要硬件來提供支持。
Mutex繼承自System.Threading.WaitHandle。
下面的例子展示了多線程中使用Mutex來實(shí)現(xiàn)同步。
class Program{static Mutex m = new Mutex();static void Main(string[] args){Task t1 = new Task(() => CriticalRegion());Task t2 = new Task(() => CriticalRegion());t1.Start();t2.Start();Task.WaitAll(t1, t2);Console.WriteLine("Finished.");Console.ReadLine();}static void CriticalRegion(){m.WaitOne();try{//臨界區(qū)域Thread.Sleep(5000);//}finally{m.ReleaseMutex();}}}Mutex的WaitOne方法獲取成功以后,互斥體將被線程獲取并且標(biāo)記為未觸發(fā)。除非擁有互斥體的線城市防它并且重新回到未觸發(fā)狀態(tài),否則其他線程都不能獲得這個(gè)互斥體。釋放的方法為ReleaseMutex。
如果有多個(gè)線程在等待同一個(gè)Mutex,那么內(nèi)核將通過FIFO算法來跟蹤等待著并決定喚醒哪一個(gè)線程。雖然在內(nèi)核中有一定的順序,但是我們不能保證我們的多線程程序能夠按順序執(zhí)行WaitOne方法。所以對我們上層應(yīng)用來說,首先喚醒哪個(gè)線程是未知的。但是你可以通過使用線程的優(yōu)先級(jí)來提升線程首先被執(zhí)行的可能性。
static void TestPriority() { for (int i = 0; i < 10; i++) { string taskName = "t" + i; Thread t1 = new Thread(() => CriticalRegionWithName(taskName)); ? if (i == 6) { t1.Priority = ThreadPriority.Highest; } t1.IsBackground = true; t1.Start(); } } ? static void CriticalRegionWithName(string name) { m.WaitOne(); try { //臨界區(qū)域 Thread.Sleep(2000); Console.WriteLine(name); // } finally { m.ReleaseMutex(); } }?
互斥體對象支持遞歸獲取,這意味著當(dāng)擁有互斥體的線程在互斥體上再次進(jìn)行等待的時(shí)候,這個(gè)等待將馬上被滿足,即使對象處于未觸發(fā)狀態(tài)。在互斥體內(nèi)部,內(nèi)核維護(hù)者一個(gè)計(jì)數(shù)器,對于每個(gè)互斥體來說,初始值為0,每次執(zhí)行獲取操作WaitOne的時(shí)候,都會(huì)增加1,而每次釋放Release的時(shí)候都會(huì)減去1。只有這個(gè)互斥體的計(jì)數(shù)器降為0的時(shí)候,其他線程才能夠重新獲取這個(gè)互斥體。所以在使用互斥體Mutex的時(shí)候一定要記得調(diào)用ReleaseMutex釋放。
static void TestRecursion() { Task t1 = new Task(() => CriticalRegionWithRecursion("t1")); Task t2 = new Task(() => CriticalRegionWithRecursion("t2")); t1.Start(); t2.Start(); ? Task.WaitAll(t1, t2); Console.WriteLine("Finished."); Console.ReadLine(); } ? static void CriticalRegionWithRecursion(string taskName) { m.WaitOne(); Console.WriteLine(taskName + " got the mutex"); try { try { m.WaitOne(); Console.WriteLine(taskName + " got the mutex"); //臨界區(qū)域 Thread.Sleep(3000); } finally { m.ReleaseMutex(); Console.WriteLine(taskName + " released the mutex"); } //臨界區(qū)域 Thread.Sleep(5000); } finally { m.ReleaseMutex(); Console.WriteLine(taskName + " released the mutex"); } }結(jié)果:
?
Mutex還是已有有Owner的鎖對象,和Monitor一樣。只有獲取了帶鎖的對象才能釋放它,如果獲取的線程和釋放的線程不是同一個(gè)線程的話將會(huì)產(chǎn)生異常: 從不同步的代碼塊中調(diào)用了對象同步方法。
測試代碼:
static void TestOwner() { Task.Factory.StartNew(() => { m.WaitOne(); Thread.Sleep(5000); }); Thread.Sleep(2000); Task.Factory.StartNew(() => { m.ReleaseMutex(); }); }?
Mutex可以作為機(jī)器范圍的,也可以用作進(jìn)程范圍的。在進(jìn)程范圍中,我們可以使用它來同步線程,而在機(jī)器范圍中,我們可以用Mutex來同步進(jìn)程。進(jìn)程同步和線程同步類似,即保證同步的訪問在進(jìn)程間共享的數(shù)據(jù)。常見的進(jìn)程間同步是用Mutex來保證進(jìn)程的在機(jī)器上的單實(shí)例運(yùn)行。在內(nèi)核對象中,同樣可以使用與進(jìn)程間同步的還有Semaphore。
static void Main(string[] args) { CheckProcessExists(); Console.ReadLine(); } static void CheckProcessExists() { using (var mutex = new Mutex(false, "Global\\Demo")) { if (!mutex.WaitOne(TimeSpan.FromSeconds(3), false)) { Console.WriteLine("Another app instance is running. Bye!"); return; } Console.WriteLine("Runing..."); Console.ReadLine(); } }?
附上MSDN上面的說明 (http://msdn.microsoft.com/zh-cn/library/system.threading.mutex.aspx):
?
擁有該Mutex互斥體的線程在結(jié)束之前沒有正確的釋放Mutex,比如忘記寫finally,忘記ReleaseMutex,或者在遞歸獲取Mutex的過程中計(jì)數(shù)器出現(xiàn)錯(cuò)誤,又或者進(jìn)程或線程突然中止,那么這個(gè)Mutex就會(huì)被認(rèn)為是廢棄的互斥體。當(dāng)互斥體被廢棄時(shí),如果不借助操作系統(tǒng)的幫助,那么其他的線程將不能獲取到該Mutex,因?yàn)榇藭r(shí)互斥體并沒有被釋放。也不用太擔(dān)心,操作系統(tǒng)會(huì)作處理,操作系統(tǒng)可以保證黨出現(xiàn)廢棄的互斥體時(shí)候,有一個(gè)等待線程可以被喚醒獲取到該互斥體。
Mutex互斥體由于是內(nèi)核對象,在使用過程中會(huì)有大量的內(nèi)核切換,所以它的效率不是很高。執(zhí)行的速度約比lock(Monitor)鎖慢50倍左右。所以在我們多線程編程中,Mutex的使用頻率并不是很高。
?
測試代碼在這里下載
轉(zhuǎn)載于:https://www.cnblogs.com/haoxinyue/archive/2013/01/29/2882152.html
總結(jié)
以上是生活随笔為你收集整理的.Net CLR 中的同步机制(一): 互斥体的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: symbian 获取手机型号
- 下一篇: 惰性渲染的一个插件