• 关于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.         }  
  • 相关阅读:
    不使用C++ 11的整数转字符串
    1090 危险品装箱(25 分)
    C++中vector,set,map自定义排序
    D
    7-2 幼儿园数学题(29 分)
    李白打酒
    C++ string和int相互转换
    1049 数列的片段和(20)(20 分)
    11. 盛最多水的容器
    7. 整数反转
  • 原文地址:https://www.cnblogs.com/zhangchenliang/p/2610385.html
Copyright © 2020-2023  润新知