.Net學習難點討論系列5 – 線程同步問題之二

接上篇文章,來說一下託管代碼包裝Windows內核對象完成線程同步的方法

Windows提供的用於同步的內核對象包括:互斥體、信號量和事件。CLR中System.Threading命名空間下的WaitHandle 類(抽象基類)完成了對這些內核對象的包裝,包裝內核對象在託管方式下實現同步的類也自然都繼承自WaitHandle類,包括 Mutex,Semaphore與EventWaitHandle(基類)。以上提到的這四個類都覆寫了WaitOne()方法並實現了 WaitAll()、WaitAny()與SignalAndWait()靜態方法,這四個方法分別允許等待對象收到信號或者數組中的所有對象收到信號, 等待數組中至少一個對象收到信號以及向一個對象發出信號並等待另一個對象。以上這些類必須實例化後纔可以使用,因此這裏所要考慮的對象不是被同步的對象而 是用於同步的對象,也就意味着作爲參數傳遞給WaitAll()、WaitAny()以及SignalAndWait()靜態方法的對象本身全部是互斥 體、事件或者信號量。

首先看一下互斥體的使用

方法5:使用Mutex類

使用互斥體可以在同一臺機器甚至是多臺機器上(需要.Net Remoting配合)的多個進程中使用同一個互斥體。跨多進程同步是Mutex相對與Monitor類的最大特點,所以,如果需要同步的訪問處於一個進 程之中,應該優先使用Monitor代替互斥體,前者的效率要高很多。

下面示例構建了一個有名互斥體來保護對資源的訪問,該互斥體被同一臺機器上的多個進程共享。 

static   void  Main() {
      
//  創建名爲‘MutexTest’的互斥體
      Mutex mutexFile  =   new  Mutex( false " MutexTest " );
      
for  ( int  i  =   0 ; i  <   10 ; i ++ ){
         mutexFile.WaitOne();
         
//  打開文件,寫入‘Hello i’並關閉文件。
         FileInfo fi  =   new  FileInfo( " tmp.txt " );
         StreamWriter sw 
=  fi.AppendText();
         sw.WriteLine(
" Hello {0} " , i);
         sw.Flush();
         sw.Close();
         System.Console.WriteLine(
" Hello {0} " , i);
         
//  等待1秒以展示互斥體的作用
         Thread.Sleep( 1000 );
         mutexFile.ReleaseMutex();
      }
      mutexFile.Close();
   }

 

WaitOne()方法會阻塞當前線程,知道獲得互斥體,使用 ReleaseMutex() 方法可以釋放互斥體。

注意,程序中 new Mutex() 不一定是創建了一個互斥體,可能是創建一個對互斥體 "MutexTest" 的引用。只有當該互斥體不存在時,系統才創建它。(只有沒有其他進程引用時, Close() 才真正銷燬它)。

方法 6 :事件

事件被用於在線程之間傳遞通知,該通知表示某個事件發生了。被關注的事件與 AutoResetEvent ManualResetEvent 這兩個事件類的一個實例關聯。(使用 EventResetMode AutoReset ManualReset 兩個枚舉類型之一可以避免創建一個新實例)

具體來說一個線程通過調用代表事件的對象的 WaitOne() 這個阻塞方法來等待一個事件被觸發。另一個線程調用事件對象的 Set() 方法以觸發事件從而使一個線程繼續執行。

static  EventWaitHandle[] events;
   
static   void  Main() {
      events 
=   new  EventWaitHandle[ 2 ];
      
//  初始化事件狀態:false 
      events[ 0 =   new  EventWaitHandle(  false  ,EventResetMode.AutoReset );
      events[
1 =   new  EventWaitHandle(  false  ,EventResetMode.AutoReset );
      Thread t0 
=   new  Thread( ThreadProc0 );
      Thread t1 
=   new  Thread( ThreadProc1 );
      t0.Start(); t1.Start();
      AutoResetEvent.WaitAll( events );
      Console.WriteLine( 
" MainThread: Thread0 reached  2 "     +
                         
"  and Thread1 reached 3. "   );
      t0.Join();t1.Join();
   }
   
static   void  ThreadProc0() {
      
for  (  int  i  =   0 ; i  <   5 ; i ++  ) {
         Console.WriteLine( 
" Thread0: {0} " , i );
         
if  ( i  ==   2  ) events[ 0 ].Set();
         Thread.Sleep( 
100  );
      }
   }
   
static   void  ThreadProc1() {
      
for  (  int  i  =   0 ; i  <   5 ; i ++  ) {
         Console.WriteLine( 
" Thread1: {0} " , i );
         
if  ( i  ==   3  ) events[ 1 ].Set();
         Thread.Sleep( 
60  );
      }
   }

 

手動重置與自動重置的區別:如果幾個線程在等待同一個自動重置的事件,那麼該事件需要爲每個線程都觸發一次,而在手動重置的情況下只需要簡單的觸發一次事件就可以讓所有的阻塞線程繼續執行。

方法 7 :信號量

信號量 – Semaphore 類的實例用來限制對一個資源的併發訪問數。當調用 WaitOne() 方法試圖進入一個信號量時,如果當時已經進入的線程數量達到了某個最大值時,該線程就被阻塞。這個最大的入口的數量由 Semaphore 類的構造函數的第二個參數設定,而第一個參數則定義了初始時的入口數量。如果第一個參數的值小於第二個,線程在調用構造函數時會自動地佔據二者插值數量的入口。最後這點也說明了同一個線程可以佔據同一個信號量的多個入口。

示例:  

static  Semaphore semaphore;
   
static   void  Main() {
      
//  初始化空的槽數              : 2.
      
//  可同時使用的槽的最大數量    : 5.
      
//  主線程擁有的槽數            : 3 (5-2).
      semaphore  =   new  Semaphore(  2 5  );
      
for  (  int  i  =   0 ; i  <   3 ++ i ) {
         Thread t 
=   new  Thread( WorkerProc );
         t.Name 
=   " Thread "   +  i;
         t.Start();
         Thread.Sleep( 
30  );
      }
   }
   
static   void  WorkerProc() {
      
for  (  int  j  =   0 ; j  <   3 ; j ++  ) {
         semaphore.WaitOne();
         Console.WriteLine( Thread.CurrentThread.Name 
+   " : Begin "  );
         
//  模擬一個.2s的任務
         Thread.Sleep(  200  );
         Console.WriteLine( Thread.CurrentThread.Name 
+   " : End "  );
         semaphore.Release();
      }
   }

 

以上程序中,主線程佔據了該信號量的3 個入口,迫使另外 3 個線程去共享剩下的 2 個入口。從運行結果你可以看出併發工作的子線程數從未超過 2 個。

 

目前我瞭解到的線程同步方法就這兩篇中介紹的 7 種,歡迎補充!

發佈了1 篇原創文章 · 獲贊 0 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章