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