關於C#中AutoResetEvent和ManualResetEvent的一點學習心得


C#中的AutoResetEvent和ManualResetEvent用於實現線程同步。其基本工作原理是多個線程持有同一個XXXResetEvent,在這個XXXResetEvent未被set前,各線程都在WaitOne()除掛起;在這個XXXResetEvent被set后,所有被掛起的線程中有一個(AutoResetEvent的情況下)或全部(ManualResetEvent的情況下)恢復執行。

 

AutoResetEvent與ManualResetEvent的差別在於某個線程在WaitOne()被掛起后重新獲得執行權時,是否自動reset這個事件(Event),前者是自動reset的,后者不是。所以從這個角度上也可以解釋上段提到的“在這個XXXResetEvent被set后,所有被掛起的線程中有一個(AutoResetEvent的情況下)或全部(ManualResetEvent的情況下)恢復執行”——因為前者一旦被某個線程獲取后,立即自動reset這個事件(Event),所以其他持有前者的線程之后WaitOne()時又被掛起;而后者被某個獲取后,不會自動reset這個事件(Event),所以后續持有后者的線程在WaitOne()時不會被掛起。

 

 

C-sharp代碼 
  1. namespace AutoResetEvent_Examples {  
  2.     class MyMainClass {  
  3.         /*  
  4.          * 構造方法的參數設置成false后,表示創建一個沒有被set的AutoResetEvent 
  5.          * 這就導致所有持有這個AutoResetEvent的線程都會在WaitOne()處掛起 
  6.          * 此時如果掛起的線程數比較多,那么你看一下自己的內存使用量……。 
  7.          * 如果將參數設置成true,表示創建一個被set的AutoResetEvent 
  8.          * 持有這個AutoResetEvent的線程們會競爭這個Event 
  9.          * 此時,在其他條件滿足的情況下 
  10.          * 至少會有一個線程得到執行 
  11.          * 而不是因得不到Event而導致所有線程都得不到執行 
  12.         */  
  13.         static AutoResetEvent myResetEvent = new AutoResetEvent(false);  
  14.         static int _Count = 0;  
  15.         static void Main() {  
  16.             Thread myThread = null;  
  17.             for(int i = 0;i < 100;i++) {  
  18.                 myThread = new Thread(new ThreadStart(MyThreadProc));  
  19.                 myThread.Name = "Thread" + i;  
  20.                 myThread.Start();  
  21.             }  
  22.             myResetEvent.Set();  
  23.             Console.Read();  
  24.         }  
  25.         static void MyThreadProc() {  
  26.             myResetEvent.WaitOne();  
  27.             _Count++;  
  28.             Console.WriteLine("In thread:{0},label={1}.",Thread.CurrentThread.Name,_Count);  
  29.              
  30.             myResetEvent.Set();  
  31.         }  
  32.     }  
  33. }  

 

 

C-sharp代碼 
  1. namespace ManualResetEvent_Examples {  
  2.     class MyMainClass {  
  3.         /*  
  4.          * 構造方法的參數設置成false后,表示創建一個沒有被set的ManualResetEvent 
  5.          * 這就導致所有持有這個ManualResetEvent的線程都會在WaitOne()處掛起 
  6.          * 此時如果掛起的線程數比較多,那么你看一下自己的內存使用量……。 
  7.          * 如果將參數設置成true,表示創建一個被set的ManualResetEvent 
  8.          * 持有這個ManualResetEvent的線程們在其他條件滿足的情況下 
  9.          * 會同時得到執行(注意,是同時得到執行!所以本例中的_Count的結果一般是不正確的^_^) 
  10.          * 而不是因得不到Event而導致所有線程都得不到執行 
  11.         */  
  12.         static ManualResetEvent myResetEvent = new ManualResetEvent(false);  
  13.         static int _Count = 0;  
  14.         static void Main() {  
  15.             Thread myThread = null;  
  16.             for(int i = 0;i < 1000;i++) {  
  17.                 myThread = new Thread(new ThreadStart(MyThreadProc));  
  18.                 myThread.Name = "Thread" + i;  
  19.                 myThread.Start();  
  20.             }  
  21.             myResetEvent.Set();  
  22.             Console.Read();  
  23.         }  
  24.         static void MyThreadProc() {  
  25.             myResetEvent.WaitOne();  
  26.             _Count++;  
  27.             /* 
  28.              * 在new ManualResetEvent(false);的情況下 
  29.              * 下面的輸出結果可能比較詭異:多個線程都輸出label=1000! 
  30.              * 一種可能的原因是多個線程在各自執行到_Count++后,被掛起 
  31.              * 隨后打印的_Count值就不是本線程中剛剛修改過的_Count值了。 
  32.              */  
  33.             Console.WriteLine("In thread:{0},_Count={1}.",Thread.CurrentThread.Name,_Count);  
  34.         }  
  35.     }  
  36. }  

 

 

set是讓事件(Event)發生,而reset是讓事件(Event)復位或者說忽略已經的事件(Event)。WaitOne是等待事件(Event)的發生,之后繼續向下執行,否則一直等待。

 

在構造AutoResetEvent和ManualResetEvent的時候,它們的構造方法里需要一個參數initState,中文版MSDN(2005和2008)上的解釋是“若要將初始狀態設置為終止,則為 true;若要將初始狀態設置為非終止,則為false。”,我看了一個下午,沒弄明白,而看一下英文版后大概就明白了“A value that you set totrueto set the initial state of the specified event to signaled. Set this value tofalseto set the initial state of the event to nonsignaled.”(參見:http://msdn.microsoft.com/en-us/library/ee432364.aspx),大體意思是說這個參數決定是否在構造這個Event的時候就設置它為“發生”狀態(signaled),如果是,則設置為true,也就是說持有這個Event的一個或多個線程在一開始就可以執行,而不需要掛起,至少是不會全部掛起(持有AutoResetEvent的一個或多個線程在任意時刻至多有一個線程在執行;持有ManualResetEvent的一個或多個線程會同時執行),否則為false(持有AutoResetEvent和ManualResetEvent的所有線程都將掛起,因為事件(Event)沒有被set,即事件沒有發生)。

 

另外稍微提一下,我在做多線程測試的時候,發現在線程數少的情況下,即使多個線程不做任何同步,如果對一個公共變量進行非互斥式修改時,不會至少很難出現不一致的情況,比如開100個線程,這個線程不做任何同步就分別給一個公共變量執行加1操作,那么結果在絕絕絕大部分的情況下是100!所以,我最后就下了狠心,把線程數增加到1000個,這個時候才出現問題,但問題也不是想象得那么嚴重——結果在991-1000之間!

 

再有,MSDN上對Monitor的Wait和Pulse兩個方法用法的舉例會導致死鎖,一種死鎖的執行順序是:

1、線程tSecond在SecondThread()中執行到while(Monitor.Wait(m_smplQueue,1000))后,釋放m_smplQueue的鎖,線程tSecond掛起;

2、線程tFirst在FirstThread()中執行到Monitor.Wait(m_smplQueue)之前耗費的時間超過1000毫秒,此時線程tSecond退出,線程tFirst掛起,並且從此以后不會被恢復!

可以使用如下改動過的代碼驗證:

 

C-sharp代碼 
  1. public void FirstThread() {  
  2.             int counter = 0;  
  3.             lock(m_smplQueue) {  
  4.                 Console.WriteLine("11");  
  5.                 while(counter < MAX_LOOP_TIME) {  
  6.                     //Wait, if the queue is busy.  
  7.                     Console.WriteLine("12");  
  8.                     Monitor.Wait(m_smplQueue);  
  9.                     Console.WriteLine("13");  
  10.                     //Push one element.  
  11.                     m_smplQueue.Enqueue(counter);  
  12.                     Console.WriteLine("14");  
  13.                     //Release the waiting thread.  
  14.                     Monitor.Pulse(m_smplQueue);  
  15.                     Console.WriteLine("15");  
  16.                     counter++;  
  17.                     Console.WriteLine("16");  
  18.                 }  
  19.             }  
  20.         }  
  21.         public void SecondThread() {  
  22.             lock(m_smplQueue) {  
  23.                 Console.WriteLine("21");  
  24.                 //Release the waiting thread.  
  25.                 Monitor.Pulse(m_smplQueue);  
  26.                 Console.WriteLine("22");  
  27.                 //Wait in the loop, while the queue is busy.  
  28.                 //Exit on the time-out when the first thread stops.   
  29.                 while(Monitor.Wait(m_smplQueue,1000)) {  
  30.                     Console.WriteLine("23");  
  31.                     //Pop the first element.  
  32.                     int counter = (int) m_smplQueue.Dequeue();  
  33.                     Console.WriteLine("24");  
  34.                     //Print the first element.  
  35.                     Console.WriteLine(counter.ToString());  
  36.                     Console.WriteLine("25");  
  37.                     //Release the waiting thread.  
  38.                     Monitor.Pulse(m_smplQueue);  
  39.                     Console.WriteLine("26");  
  40.                 }  
  41.                 Console.WriteLine("27");  
  42.             }  
  43.         }  

 


免責聲明!

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



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