C#線程同步與死鎖Monitor


在上一講介紹了使用lock來實現C#線程同步。實際上,這個lock是C#的一個障眼法,在C#編譯器編譯lock語句時,將其編譯成了調用Monitor類。先看看下面的C#源代碼:

 
 
 
         
  1. public static void MyLock() 
  2.     lock (typeof(Program)) 
  3.     { 
  4.     } 
  5.  

上面的代碼通過lock語句使MyLock同步,這個方法被編譯成IL后,代碼如圖1所示。

代碼如圖1

圖1

從上圖被標注的區域可以看到,一條lock語句被編譯成了調用Monitor的Enter和Exit方法。Monitor在 System.Threading命名空間中。lock的功能就相當於直接調用Monitor的Entry方法,所不同的是,lock方法在結束后,會自動解除鎖定,當然,在IL中是調用了Monitor的Exit方法,但在C#程序中,看起來是自動解鎖的,這類似於C#中的using語句,可以自動釋放數據庫等的資源。但如果直接在C#源程序中使用Monitor類,就必須調用Exit方法來顯式地解除鎖定。如下面的代碼所示:

 
 
 
         
  1. Monitor.Entry(lockObj); 
  2. try
  3.     // lockObj的同布區 
  4. catch(Exception e) 
  5.     // 異常處理代碼 
  6. finally
  7.     Monitor.Exit(lockObj);  // 解除鎖定 
  8.  

Exit方法最后在finally里調用,這樣無論在方法在發生異常、返回還是正常執行,都會執行到finally,並調用Exit方法解除鎖定。

Monitor類不僅可以完全取代lock語句(如果只使用lock語句本身的功能,最好還是直接用lock語句吧),還可以使用TryEntry方法設置一個鎖定超時,單位是毫秒。如下面的代碼所示:

 
 
 
         
  1. if(Monitor.TryEntry(lockObj, 1000)) 
  2.     try
  3.     { 
  4.     } 
  5.     finally
  6.     { 
  7.         Monitor.Exit(lockObj); 
  8.     } 
  9. else
  10.     // 超時后的處理代碼 
  11.  

上面的代碼設置了鎖定超時時間為1秒,也就是說,在1秒中后,lockObj還未被解鎖,TryEntry方法就會返回false,如果在1秒之內,lockObj被解鎖,TryEntry返回true。我們可以使用這種方法來避免死鎖,如下面的代碼所示:

 
 
 
         
  1. class Program 
  2.     private static Object objA = new Object(); 
  3.     private static Object objB = new Object(); 
  4.     public static void LockA() 
  5.     { 
  6.         if (Monitor.TryEnter(objA, 1000)) 
  7.         { 
  8.             Thread.Sleep(1000); 
  9.             if (Monitor.TryEnter(objB, 2000)) 
  10.             { 
  11.                 Monitor.Exit(objB); 
  12.             } 
  13.             else
  14.             { 
  15.  
  16.                 Console.WriteLine("LockB timeout"); 
  17.             } 
  18.             Monitor.Exit(objA); 
  19.         } 
  20.         Console.WriteLine("LockA"); 
  21.     } 
  22.     public static void LockB() 
  23.     { 
  24.         if (Monitor.TryEnter(objB, 2000)) 
  25.         { 
  26.             Thread.Sleep(2000); 
  27.             if (Monitor.TryEnter(objA, 1000)) 
  28.             { 
  29.                 Monitor.Exit(objA); 
  30.             } 
  31.             else
  32.             { 
  33.                 Console.WriteLine("LockA timeout"); 
  34.             } 
  35.             Monitor.Exit(objB); 
  36.         } 
  37.         Console.WriteLine("LockB"); 
  38.     } 
  39.     public static void Main() 
  40.     { 
  41.         Thread threadA = new Thread(LockA); 
  42.         Thread threadB = new Thread(LockB); 
  43.         threadA.Start(); 
  44.         threadB.Start(); 
  45.         Thread.Sleep(4000);          
  46.         Console.WriteLine("線程結束"); 
  47.     } 

上面的代碼是在上一講舉的死鎖的例子,但在這一講將lock語句改成了TryEntry方法,而且設置了鎖定超時間,由於在等待一定時間后,不管被鎖定的對象是否被解鎖,TryEntry方法都會返回,因此,上面的代碼是不會死鎖的。運行上面的代碼的結果如圖2所示。

代碼的結果如圖2

圖2

如果TryEntry方法的超時時間為System.Threading.Timeout.Infinite,TryEntry方法就相當於Entry方法,如果超時時間為0,不管是否解鎖,TryEntry方法都會立即返回。

這樣就解決了C#線程同步與死鎖的問題


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM