• C#AutoResetEvent和ManualResetEvent的区别


    原文

     


    所谓线程同步,就是多个线程之间在某个对象上执行等待(也可理解为锁定该对象),直到该对象被解除锁定。C#中对象的类型分为引用类型和值类型。CLR在这两种类型上的等待是不一样的。我们可以简单的理解为在CLR中,值类型是不能被锁定的,也即:不能在一个值类型对象上执行等待。而在引用类型上的等待机制,则分为两类:锁定和信号同步。

    锁定,使用关键字lock和类型Monitor。两者没有实质区别,前者其实是后者的语法糖。这是最常用的同步技术;

    信号同步机制中涉及的类型都继承自抽象类WaitHandle,这些类型有EventWaitHandle(类型化为AutoResetEvent、ManualResetEvent)和Semaphore以及Mutex。见类图6-3:

    clip_image002

    图 同步功能类类图

    EventWaitHandle(子类为AutoResetEvent、ManualResetEvent)和Semaphore以及Mutex都继承自WaitHandle,所以它们底层的原理是一致的,维护的都是一个系统内核句柄。不过我们仍需简单的区分下这三类类型。

    EventWaitHandle,维护一个由内核产生的布尔类型对象(我们称之为“阻滞状态”),如果其值为false,那么在它上面等待的线程就阻塞。可以调用类型的Set方法将其值设置为true,解除阻塞。EventWaitHandle类型的两个子类AutoResetEvent和ManualResetEvent,它们的区别并不大,本建议接下来会针对它们阐述如何正确使用信号量。

    Semaphore,维护一个由内核产生的整型变量,如果其值为0,则在它上面等待的线程就阻塞,其值大于0,就解除阻塞,同时,每解除阻塞一个线程,其值就减1。

    EventWaitHandle和Semaphore提供的都是单应用程序域内的线程同步功能,Mutex则不同,它为我们提供了跨应用程序域阻塞和解除阻塞线程的能力。

    一:终止状态和非终止状态

    首先说说线程的终止状态和非终止状态。AutoResetEvent和ManualResetEvent的构造函数中,都有bool变量来指明线程的终止状态和非终止状态。true表示终止状态(个人理解也就是可运行状态,根据理解应该是该线程的阻塞终止了),false表示非终止状态。

    
            AutoResetEvent _autoResetEvent = new AutoResetEvent(false);
            private void BT_Temp_Click(object sender, RoutedEventArgs e)
            {
                Thread t1 = new Thread(this.Thread1Foo);
                t1.Start();
                Thread.Sleep(3000); //Thread.Sleep(Int32)是批当前进程挂起3000毫秒,与线程t1是一点关系也没有的。
                _autoResetEvent.Set();
            }
     
            void Thread1Foo()
            {
                _autoResetEvent.WaitOne();
                MessageBox.Show("t1 end");
            }
                

    这段代码的执行结果,就是3秒钟过后,弹出“t1 end”。

    而如果把:

    
               AutoResetEvent _autoResetEvent = new AutoResetEvent(false);
                
    改为:
    
             AutoResetEvent _autoResetEvent = new AutoResetEvent(true);
                

    二:AutoResetEvent和ManualResetEvent的区别

    AutoResetEvent 允许线程通过发信号互相通信。 通常,当线程需要独占访问资源时使用该类。

    线程通过调用 AutoResetEvent 上的 WaitOne 来等待信号。 如果 AutoResetEvent 为非终止状态,则线程会被阻止,并等待当前控制资源的线程通过调用 Set来通知资源可用。

    调用 Set 向 AutoResetEvent 发信号以释放等待线程。 AutoResetEvent 将保持终止状态,直到一个正在等待的线程被释放,然后自动返回非终止状态。 如果没有任何线程在等待,则状态将无限期地保持 为终止状态。如果当 AutoResetEvent 为终止状态时线程调用 WaitOne,则线程不会被阻止。 AutoResetEvent 将立即释放线程并返回到非终止状态。

    
            AutoResetEvent _autoResetEvent = new AutoResetEvent(false);
            private void BT_Temp_Click(object sender, RoutedEventArgs e)
            {
                Thread t1 = new Thread(this.Thread1Foo);
                t1.Start();
                Thread t2 = new Thread(this.Thread2Foo);
                t2.Start();
                Thread.Sleep(3000);
                _autoResetEvent.Set();
            }
     
            void Thread1Foo()
            {
                _autoResetEvent.WaitOne();
                MessageBox.Show("t1 end");
            }
     
            void Thread2Foo()
            {
                _autoResetEvent.WaitOne();
                MessageBox.Show("t2 end");
            }
                

    该段代码运行的效果是,过3秒后,要么弹出“t1 end”,要么弹出“t2 end”,不会两个都弹出。也就是说,其中一个进行将会结束,而另一个进程永远不会结束。

    代码段3:

    
            ManualResetEvent _menuRestEvent = new ManualResetEvent(false);
     
            private void BT_Temp_Click(object sender, RoutedEventArgs e)
            {
                Thread t1 = new Thread(this.Thread1Foo);
                t1.Start();
                Thread t2 = new Thread(this.Thread2Foo);
                t2.Start();
                Thread.Sleep(3000);
                _menuRestEvent.Set();
            }
     
            void Thread1Foo()
            {
                _menuRestEvent.WaitOne();
                MessageBox.Show("t1 end");
            }
     
            void Thread2Foo()
            {
                _menuRestEvent.WaitOne();
                MessageBox.Show("t2 end");
            }
                

    该段代码运行的效果是,过3秒后,“t1 end”和“t2 end”,两个都被弹出。也就是说,两个进程都结束了。

    这个特性就是说,AutoResetEvent只会给一个线程发送信号,而不会给多个线程发送信号。在我们需要同步多个线程的时候,就只能采用ManualResetEvent了。至于深层次的原因是,AutoResetEvent在set()之后,会将线程 状态自动置为false,而ManualResetEvent在Set()后,线程的状态就变为true了,必须手动ReSet()之后,才会重新将线程置为false。这也就是为什么他们的名字一个为Auto,一个为Manual的原因。为了更加充分的验证Manua lResetEvent的这点特性

    我们再来看代码片段4

    代码片段4:

    
            ManualResetEvent _menuRestEvent = new ManualResetEvent(false);
     
            private void BT_Temp_Click(object sender, RoutedEventArgs e)
            {
                Thread t1 = new Thread(this.Thread1Foo);
                t1.Start();
                Thread t2 = new Thread(this.Thread2Foo);
                t2.Start();
                Thread.Sleep(3000);
                _menuRestEvent.Set();
                //_menuRestEvent.Reset();           
            }
     
            void Thread1Foo()
            {
                _menuRestEvent.WaitOne();
                MessageBox.Show("t1 step1 end");
                //睡1S,用于等待主线程_menuRestEvent.Reset();
                Thread.Sleep(1000);
                _menuRestEvent.WaitOne();
                MessageBox.Show("t1 step2 end");
            }
     
            void Thread2Foo()
            {
                _menuRestEvent.WaitOne();
                MessageBox.Show("t2 step1 end");
                //睡1S,用于等待主线程_menuRestEvent.Reset();
                Thread.Sleep(1000);
                _menuRestEvent.WaitOne();
                MessageBox.Show("t2 step2 end");
            }
                

    在代码片段4中

    我们对//_menuRestEvent.Reset()进行了注释,也就是说, _menuRestEvent.Set()后,线程的状态就是true状态的,程序运行的结果是"t1 step1 end"、"t2 step2 end"、在3秒之后弹出,然后隔1s "t1 step2 end"、"t2 step2 end"弹出。

    而如果我们将//_menuRestEvent.Reset()的注释去掉, 会发现"t1 step2 end"和"t2 step2 end"永远不会弹出。除非我们在主线程中再次对_menuRestEvent进行Set()。

  • 相关阅读:
    Mybatis入门
    Spring的xml文件配置方式实现AOP
    jquery简直是太酷炫强大了
    [Google Guava] 2.2-新集合类型
    小规模的流处理框架.Part 1: thread pools
    数据库三大范式和五大约束
    Hibernate:缓存
    MyBatis:缓存配置
    Python:协程
    微信公众号开发之测试账号
  • 原文地址:https://www.cnblogs.com/wwkk/p/9784798.html
Copyright © 2020-2023  润新知