zoukankan      html  css  js  c++  java
  • C#线程同步--线程通信

    问题抽象:当某个操作的执行必须依赖于另一个操作的完成时,需要有个机制来保证这种先后关系。
    线程通信方案:ManualResetEventSlim、ManualResetEvent、AutoResetEvent
    方案特性:提供线程通知的能力,没有接到通知前,线程必须等待,有先后顺序。

    1、ManualResetEvent类
         对象有两种信号量状态True和False。构造函数设置初始状态。简单来说,
         ◆ 如果构造函数由true创建,则第一次WaitOne()不会阻止线程的执行,而是等待Reset后的第二次WaitOne()才阻止线程执行。
         ◆ 如果构造函数有false创建,则WaitOne()必须等待Set()才能往下执行。
      一句话总结就是:是否忽略第一次阻塞。
      方法如下:
           ◆ WaitOne:该方法用于阻塞线程,默认是无限期的阻塞,支持设置等待时间,如果超时就放弃阻塞,不等了,继续往下执行;
           ◆ Set:手动修改信号量为True,也就是恢复线程执行;
           ◆ ReSet:重置状态; 重置后,又能WaitOne()啦

    using System;
    using System.Threading;
    
    namespace ConsoleApp1
    {
        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();
            }
        }
    }
    Program

      在思维上,这个东西可以有两种用法,一种是让主线程等待辅助线程,一种是辅助线程等待主线程。
      但无论怎么用,都是让一个线程等待或唤醒另外一个线程。

      Reset方法调用示例

    using System;
    using System.Threading;
    
    namespace ConsoleApp1
    {
        class Program
        {
            //一开始设置为false,当遇到WaitOne()时,需要Set()才能继续执行
            static ManualResetEvent mr = new ManualResetEvent(false);
    
            public static void Main()
            {
                Thread t = new Thread(Run);
                Console.WriteLine("开始" + DateTime.Now.ToString("mm:ss"));
                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();
            }
        }
    }
    Program

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

    2、AutoResetEvent类
      AutoResetEvent与ManualResetEvent的区别在于AutoResetEvent 的WaitOne会改变信号量的值为false,让其等待阻塞。
      比如说初始信号量为True,如果WaitOne超时信号量将自动变为False,而ManualResetEvent则不会。
      第二个区别:
      ◆ ManualResetEvent:每次可以唤醒一个或多个线程
      ◆ AutoResetEvent:每次只能唤醒一个线程

    using System;
    using System.Threading;
    
    namespace ConsoleApp1
    {
        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"));
            }
        }
    }
    Program

      AutoResetEvent 允许线程通过发信号互相通信。通常,此通信涉及线程需要独占访问的资源。
      线程通过调用 AutoResetEvent 上的 WaitOne 来等待信号。如果 AutoResetEvent 处于非终止状态,则该线程阻塞,并等待当前控制资源的线程,通过调用 Set 发出资源可用的信号。调用 Set 向 AutoResetEvent 发信号以释放等待线程。AutoResetEvent 将保持终止状态,直到一个正在等待的线程被释放,然后自动返回非终止状态。如果没有任何线程在等待,则状态将无限期地保持为终止状态。可以通过将一个布尔值传递给构造函数来控制 AutoResetEvent 的初始状态,如果初始状态为终止状态,则为 true;否则为 false。
      通俗的来讲只有等myResetEven.Set()成功运行后,myResetEven.WaitOne()才能够获得运行机会;Set是发信号,WaitOne是等待信号,只有发了信号,等待的才会执行。如果不发的话,WaitOne后面的程序就永远不会执行。下面我们来举一个例子:我去书店买书,当我选中一本书后我会去收费处付钱,
    付好钱后再去仓库取书。这个顺序不能颠倒,我作为主线程,收费处和仓库做两个辅助线程,代码如下:

    using System;
    using System.Threading;
    
    namespace ConsoleApp1
    {
        class TestAutoReseEvent
        {
            static AutoResetEvent BuyBookEvent = new AutoResetEvent(false);
            static AutoResetEvent PayMoneyEvent = new AutoResetEvent(false);
            static AutoResetEvent GetBookEvent = new AutoResetEvent(false);
            static int number = 10;
    
            public static void Run()
            {
                Thread buyBookThread = new Thread(new ThreadStart(BuyBookProc));
                buyBookThread.Name = "买书线程";
                Thread payMoneyThread = new Thread(new ThreadStart(PayMoneyProc));
                payMoneyThread.Name = "付钱线程";
                Thread getBookThread = new Thread(new ThreadStart(GetBookProc));
                getBookThread.Name = "取书线程";
    
                buyBookThread.Start();
                payMoneyThread.Start();
                getBookThread.Start();
    
                buyBookThread.Join();
                payMoneyThread.Join();
                getBookThread.Join();
    
            }
    
            static void BuyBookProc()
            {
                while (number > 0)
                {
                    Console.WriteLine("{0}:数量{1}", Thread.CurrentThread.Name, number);
                    PayMoneyEvent.Set();
                    BuyBookEvent.WaitOne();
                    Console.WriteLine("------------------------------------------");
                    number--;
                }
            }
    
            static void PayMoneyProc()
            {
                while (number > 0)
                {
                    PayMoneyEvent.WaitOne();
                    Console.WriteLine("{0}:数量{1}", Thread.CurrentThread.Name, number);
                    GetBookEvent.Set();
                }
            }
    
            static void GetBookProc()
            {
                while (number > 0)
                {
                    GetBookEvent.WaitOne();
                    Console.WriteLine("{0}:数量{1}", Thread.CurrentThread.Name, number);
                    BuyBookEvent.Set();
                }
            }
        }
    }
    TestAutoReseEvent
    namespace ConsoleApp1
    {
        class Program
        {
            public static void Main()
            {
                TestAutoReseEvent.Run();
            }
        }
    }
    Program

    3、ManualResetEventSlim类
      ManualResetEventSlim是ManualResetEvent的混合版本,一直保持大门敞开直到手工调用Reset方法,
      Set() 相当于打开了大门从而允许准备好的线程接收信号并继续工作
      Reset() 相当于关闭了大门 此时已经准备好执行的信号量 则只能等到下次大门开启时才能够执行

    using System;
    using System.Threading;
    
    namespace ConsoleApp1
    {
        class Program
        {
            static void Main(string[] args)
            {
                var t1 = new Thread(() => TravelThroughGates("Thread 1", 5));
                var t2 = new Thread(() => TravelThroughGates("Thread 2", 6));
                var t3 = new Thread(() => TravelThroughGates("Thread 3", 12));
                t1.Start();
                t2.Start();
                t3.Start();
                Thread.Sleep(TimeSpan.FromSeconds(6));
                Console.WriteLine("The gates are now open!");
                _mainEvent.Set();
                Thread.Sleep(TimeSpan.FromSeconds(2));
                _mainEvent.Reset();
                Console.WriteLine("The gates have been closed!");
                Thread.Sleep(TimeSpan.FromSeconds(10));
                Console.WriteLine("The gates are now open for the second time!");
                _mainEvent.Set();
                Thread.Sleep(TimeSpan.FromSeconds(2));
                Console.WriteLine("The gates have been closed!");
                _mainEvent.Reset();
            }
    
            static void TravelThroughGates(string threadName, int seconds)
            {
                Console.WriteLine("{0} falls to sleep {1}", threadName, seconds);
                Thread.Sleep(TimeSpan.FromSeconds(seconds));
                Console.WriteLine("{0} waits for the gates to open!", threadName);
                _mainEvent.Wait();
                Console.WriteLine("{0} enters the gates!", threadName);
            }
            /// <summary>
            /// ManualResetEventSlim是ManualResetEvent的混合版本,一直保持大门敞开直到手工调用Reset方法,
            /// _mainEvent.Set 相当于打开了大门从而允许准备好的线程接收信号并继续工作
            /// _mainEvent.Reset 相当于关闭了大门 此时已经准备好执行的信号量 则只能等到下次大门开启时才能够执行
            /// </summary>
            static ManualResetEventSlim _mainEvent = new ManualResetEventSlim(false);
        }
    }
    Program
  • 相关阅读:
    003.同时Ping多个IP(select实现IO复用,信号计时),ping程序升级版
    002.ICMP--拼接ICMP包,实现简单Ping程序(原始套接字)
    001.linux下clock()检测程序运行时间
    django form的函数用法
    命令注入利用语句
    小白审计JACKSON反序列化漏洞
    代码审计小工具
    Burp插件开发--应用篇
    burp插件开发--基础篇
    JAVA web网站代码审计--入门
  • 原文地址:https://www.cnblogs.com/scmail81/p/9514952.html
Copyright © 2011-2022 走看看