• 信号量 <第六篇>


    一、ManualResetEvent

          该对象有两种信号量状态True和False。构造函数设置初始状态。简单来说,

    • 如果构造函数由true创建,则第一次WaitOne()不会阻止线程的执行,而是等待Reset后的第二次WaitOne()才阻止线程执行。
    • 如果构造函数有false创建,则WaitOne()必须等待Set()才能往下执行。

      一句话总结就是:是否忽略第一次阻塞。

      方法如下:

    • WaitOne:该方法用于阻塞线程,默认是无限期的阻塞,支持设置等待时间,如果超时就放弃阻塞,不等了,继续往下执行;
    • Set:手动修改信号量为True,也就是恢复线程执行;
    • ReSet:重置状态;
        class Program
        {
            //一开始设置为false才会等待收到信号才执行
            static ManualResetEvent mr = new ManualResetEvent(false);
            public static void Main()
            {
                Thread t = new Thread(Run);
                //启动辅助线程
                t.Start();
                //等待辅助线程执行完毕之后,主线程才继续执行
                Console.WriteLine("主线程一边做自己的事,一边等辅助线程执行!" + DateTime.Now.ToString("mm:ss"));
                mr.WaitOne();
                Console.WriteLine("收到信号,主线程继续执行" + DateTime.Now.ToString("mm:ss"));
                Console.ReadKey();
            }
    
            static void Run()
            {
                //模拟长时间任务
                Thread.Sleep(3000);
                Console.WriteLine("辅助线程长时间任务完成!" + DateTime.Now.ToString("mm:ss"));
                mr.Set();
            }
        }

      输出结果如下:

      

      在思维上,这个东西可以有两种用法,一种是让主线程等待辅助线程,一种是辅助线程等待主线程。

      但无论怎么用,都是让一个线程等待或唤醒另外一个线程。

      Reset

        class Program
        {
            //一开始设置为false,当遇到WaitOne()时,需要Set()才能继续执行
            static ManualResetEvent mr = new ManualResetEvent(false);
    
            public static void Main()
            {
                Thread t = new Thread(Run);
                t.Start();
                mr.WaitOne();
                Console.WriteLine("第一次等待完成!" + DateTime.Now.ToString("mm:ss"));
                mr.Reset();     //重置后,又能WaitOne()啦
                mr.WaitOne(3000);
                Console.WriteLine("第二次等待完成!" + DateTime.Now.ToString("mm:ss"));
                Console.ReadKey();
            }
    
            static void Run()
            {
                mr.Set();
                Thread.Sleep(2000);
                mr.Set();
            }
        }

      输出如下:

      

       如果以上代码不使用Reset,则直接输出第二次等待完成,而不会等待2秒。

    二、AutoResetEvent

      AutoResetEvent与ManualResetEvent的区别在于AutoResetEvent 的WaitOne会改变信号量的值为false,让其等待阻塞。

      比如说初始信号量为True,如果WaitOne超时信号量将自动变为False,而ManualResetEvent则不会。

      第二个区别:

    • ManualResetEvent:每次可以唤醒一个或多个线程;
    • AutoResetEvent:每次只能唤醒一个线程;
        class Program
        {
            static AutoResetEvent ar = new AutoResetEvent(true);
            public static void Main()
            {
                Thread t = new Thread(Run);
                t.Start();
    
                bool state = ar.WaitOne(1000);
                Console.WriteLine("当前的信号量状态:{0}", state);
    
                state = ar.WaitOne(1000);
                Console.WriteLine("再次WaitOne后现在的状态是:{0}", state);
    
                state = ar.WaitOne(1000);
                Console.WriteLine("再次WaitOne后现在的状态是:{0}", state);
    
                Console.ReadKey();
            }
    
            static void Run()
            {
                Console.WriteLine("当前时间" + DateTime.Now.ToString("mm:ss"));
            }
        }

      输出如下:

      

      假如要用ManualResetEvent实现上面同样的效果,Run方法就不用手动Reset()了,AutoResetEvent保证后续每个WaitOne()都有效:

        static void Run()
        {
            //线程开始执行时待命,收到信号才动身
            mr.WaitOne();//我想让辅助线程暂停3秒
            mr.WaitOne(3000);//我想让辅助线程暂停,10后由主线程再次唤醒
            mr.WaitOne();
        }

      少了手动Reset()代码。

      2014-11-13

      Workflow 4.5用的就是这个东西,因为对信号量这个东西不熟,可算吃了大亏。

    三、Semaphore

      用于控制线程的访问数量,默认的构造函数为initialCount和maximumCount,表示默认设置的信号量个数和最大信号量个数。当你WaitOne的时候,信号量自减,当Release的时候,信号量自增,然而当信号量为0的时候,后续的线程就不能拿到WaitOne了,所以必须等待先前的线程通过Release来释放。

        class Program
        {
            static void Main(string[] args)
            {
                Thread t1 = new Thread(Run1);
                t1.Start();
                Thread t2 = new Thread(Run2);
                t2.Start();
                Thread t3 = new Thread(Run3);
                t3.Start();
                Console.ReadKey();
            }
    
            //初始可以授予2个线程信号,因为第3个要等待前面的Release才能得到信号
            static Semaphore sem = new Semaphore(2, 10);
    
            static void Run1()
            {
                sem.WaitOne();
                Console.WriteLine("大家好,我是Run1;" + DateTime.Now.ToString("mm:ss"));
    
                //两秒后
                Thread.Sleep(2000);
                sem.Release();
            }
    
            static void Run2()
            {
                sem.WaitOne();
                Console.WriteLine("大家好,我是Run2;" + DateTime.Now.ToString("mm:ss"));
    
                //两秒后
                Thread.Sleep(2000);
                sem.Release();
            }
    
            static void Run3()
            {
                sem.WaitOne();
                Console.WriteLine("大家好,我是Run3;" + DateTime.Now.ToString("mm:ss"));
    
                //两秒后
                Thread.Sleep(2000);
                sem.Release();
            }
        }

      输出:

      

      在以上的方法中Release()方法相当于自增一个信号量,Release(5)自增5个信号量。但是,Release()到构造函数的第二个参数maximumCount的值就不能再自增了。

      命名Semaphore可用于进程级交互。

        class Program
        {
            static void Main(string[] args)
            {
    
                Thread t1 = new Thread(Run1);
                t1.Start();
    
                Thread t2 = new Thread(Run2);
                t2.Start();
    
                Console.Read();
            }
            
            //初始可以授予2个线程信号,因为第3个要等待前面的Release才能得到信号
            static Semaphore sem = new Semaphore(3, 10, "命名Semaphore");
    
            static void Run1()
            {
                sem.WaitOne();
    
                Console.WriteLine("进程:" +Process.GetCurrentProcess().Id + "  我是Run1" + DateTime.Now.TimeOfDay);
            }
    
            static void Run2()
            {
                sem.WaitOne();
    
                Console.WriteLine("进程:" + Process.GetCurrentProcess().Id + "  我是Run2" + DateTime.Now.TimeOfDay);
            }
        }

      输出如下:

      

      这个东西是跨进程的,如何测试,直接运行两次bin目录的exe文件,就能发现最多只能输出3个。

    四、综合示例(线程、事件、信号量)

      要搞清楚线程、信号量、事件这三者的关系。实际上3个东西并无具体联系,各自有各自的作用,但是配合起来使用,威力无穷。

      下面用一个例子,结合事件、信号量、线程来实现如下功能:

    1. 主线程启动辅助线程执行一个长时间任务;
    2. 辅助线程完成时,触发完成事件(),调用委托,让主线程继续执行;
    namespace ConsoleApplication3
    {
        class Program
        {
            static AutoResetEvent ar = new AutoResetEvent(true);
            static void MyEventHandler(object sender, EventArgs e)
            {
                ar.Set();
            }
    
            static void Main(string[] args)
            {
                LongTimeWork LTW = new LongTimeWork();
                LTW.Completed += MyEventHandler;
                Thread t = new Thread(LTW.MyLongTimeWork);
                t.Start();
                //继续忙我的
                Thread.Sleep(2000);
                //等待辅助线程完成
                ar.WaitOne();
                Console.WriteLine("主线程完成!");
                Console.ReadKey();
            }
        }
    
        public class LongTimeWork
        {
            //定义一个事件
            public event EventHandler Completed;
    
            public void MyLongTimeWork()
            {
                Thread.Sleep(1000);
                Console.WriteLine("辅助线程长时间任务完成!");
                //当辅助线程完成时,触发已完成事件
                if (Completed != null)
                {
                    Completed(this, new EventArgs());
                }
            }
        }
    }

      输出如下:

      

      以上虽然短短几十行代码,但是我却开发了两年多.Net之后才能够领悟。其主要作用是什么,以上达到了线程控制的目的,当我们开发一个核心模块时(LongTimeWork),仅仅暴露出一个事件(Completed),调用的人配合上信号量(AutoResetEvent),就能够随意调用你的核心模块。这也是WF4的调用方式。

  • 相关阅读:
    springboot集成Spring Security安全框架(一)入门程序
    Redis学习(三)Redis 配置
    javax.servlet.ServletException: Could not resolve view with name 'order/list' in servlet with name 'dispatcherServlet'
    com.mysql.cj.exceptions.DataReadException: Zero date value prohibited
    Redis学习(二)Redis的安装
    Redis学习(一)简介
    Registering current configuration as safe fallback point
    IntelliJ IDEA为类和方法自动添加注释
    github 创建新项目
    pypi 的使用
  • 原文地址:https://www.cnblogs.com/kissdodog/p/2988646.html
Copyright © 2020-2023  润新知