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

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

  1. using System;
  2. using System.Threading;
  3.  
  4. namespace ConsoleApp1
  5. {
  6. class Program
  7. {
  8. //一开始设置为false才会等待收到信号才执行
  9. static ManualResetEvent mr = new ManualResetEvent(false);
  10. public static void Main()
  11. {
  12. Thread t = new Thread(Run);
  13. //启动辅助线程
  14. t.Start();
  15. //等待辅助线程执行完毕之后,主线程才继续执行
  16. Console.WriteLine("主线程一边做自己的事,一边等辅助线程执行!" + DateTime.Now.ToString("mm:ss"));
  17. mr.WaitOne();
  18. Console.WriteLine("收到信号,主线程继续执行" + DateTime.Now.ToString("mm:ss"));
  19. Console.ReadKey();
  20. }
  21.  
  22. static void Run()
  23. {
  24. //模拟长时间任务
  25. Thread.Sleep();
  26. Console.WriteLine("辅助线程长时间任务完成!" + DateTime.Now.ToString("mm:ss"));
  27. mr.Set();
  28. }
  29. }
  30. }

Program

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

  Reset方法调用示例

  1. using System;
  2. using System.Threading;
  3.  
  4. namespace ConsoleApp1
  5. {
  6. class Program
  7. {
  8. //一开始设置为false,当遇到WaitOne()时,需要Set()才能继续执行
  9. static ManualResetEvent mr = new ManualResetEvent(false);
  10.  
  11. public static void Main()
  12. {
  13. Thread t = new Thread(Run);
  14. Console.WriteLine("开始" + DateTime.Now.ToString("mm:ss"));
  15. t.Start();
  16. mr.WaitOne();
  17. Console.WriteLine("第一次等待完成!" + DateTime.Now.ToString("mm:ss"));
  18. mr.Reset(); //重置后,又能WaitOne()啦
  19. mr.WaitOne();
  20. Console.WriteLine("第二次等待完成!" + DateTime.Now.ToString("mm:ss"));
  21. Console.ReadKey();
  22. }
  23.  
  24. static void Run()
  25. {
  26. mr.Set();
  27. Thread.Sleep();
  28. mr.Set();
  29. }
  30. }
  31. }

Program

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

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

  1. using System;
  2. using System.Threading;
  3.  
  4. namespace ConsoleApp1
  5. {
  6. class Program
  7. {
  8. static AutoResetEvent ar = new AutoResetEvent(true);
  9. public static void Main()
  10. {
  11. Thread t = new Thread(Run);
  12. t.Start();
  13.  
  14. bool state = ar.WaitOne();
  15. Console.WriteLine("当前的信号量状态:{0}", state);
  16.  
  17. state = ar.WaitOne();
  18. Console.WriteLine("再次WaitOne后现在的状态是:{0}", state);
  19.  
  20. state = ar.WaitOne();
  21. Console.WriteLine("再次WaitOne后现在的状态是:{0}", state);
  22.  
  23. Console.ReadKey();
  24. }
  25.  
  26. static void Run()
  27. {
  28. Console.WriteLine("当前时间" + DateTime.Now.ToString("mm:ss"));
  29. }
  30. }
  31. }

Program

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

  1. using System;
  2. using System.Threading;
  3.  
  4. namespace ConsoleApp1
  5. {
  6. class TestAutoReseEvent
  7. {
  8. static AutoResetEvent BuyBookEvent = new AutoResetEvent(false);
  9. static AutoResetEvent PayMoneyEvent = new AutoResetEvent(false);
  10. static AutoResetEvent GetBookEvent = new AutoResetEvent(false);
  11. static int number = ;
  12.  
  13. public static void Run()
  14. {
  15. Thread buyBookThread = new Thread(new ThreadStart(BuyBookProc));
  16. buyBookThread.Name = "买书线程";
  17. Thread payMoneyThread = new Thread(new ThreadStart(PayMoneyProc));
  18. payMoneyThread.Name = "付钱线程";
  19. Thread getBookThread = new Thread(new ThreadStart(GetBookProc));
  20. getBookThread.Name = "取书线程";
  21.  
  22. buyBookThread.Start();
  23. payMoneyThread.Start();
  24. getBookThread.Start();
  25.  
  26. buyBookThread.Join();
  27. payMoneyThread.Join();
  28. getBookThread.Join();
  29.  
  30. }
  31.  
  32. static void BuyBookProc()
  33. {
  34. while (number > )
  35. {
  36. Console.WriteLine("{0}:数量{1}", Thread.CurrentThread.Name, number);
  37. PayMoneyEvent.Set();
  38. BuyBookEvent.WaitOne();
  39. Console.WriteLine("------------------------------------------");
  40. number--;
  41. }
  42. }
  43.  
  44. static void PayMoneyProc()
  45. {
  46. while (number > )
  47. {
  48. PayMoneyEvent.WaitOne();
  49. Console.WriteLine("{0}:数量{1}", Thread.CurrentThread.Name, number);
  50. GetBookEvent.Set();
  51. }
  52. }
  53.  
  54. static void GetBookProc()
  55. {
  56. while (number > )
  57. {
  58. GetBookEvent.WaitOne();
  59. Console.WriteLine("{0}:数量{1}", Thread.CurrentThread.Name, number);
  60. BuyBookEvent.Set();
  61. }
  62. }
  63. }
  64. }

TestAutoReseEvent

  1. namespace ConsoleApp1
  2. {
  3. class Program
  4. {
  5. public static void Main()
  6. {
  7. TestAutoReseEvent.Run();
  8. }
  9. }
  10. }

Program

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

  1. using System;
  2. using System.Threading;
  3.  
  4. namespace ConsoleApp1
  5. {
  6. class Program
  7. {
  8. static void Main(string[] args)
  9. {
  10. var t1 = new Thread(() => TravelThroughGates("Thread 1", ));
  11. var t2 = new Thread(() => TravelThroughGates("Thread 2", ));
  12. var t3 = new Thread(() => TravelThroughGates("Thread 3", ));
  13. t1.Start();
  14. t2.Start();
  15. t3.Start();
  16. Thread.Sleep(TimeSpan.FromSeconds());
  17. Console.WriteLine("The gates are now open!");
  18. _mainEvent.Set();
  19. Thread.Sleep(TimeSpan.FromSeconds());
  20. _mainEvent.Reset();
  21. Console.WriteLine("The gates have been closed!");
  22. Thread.Sleep(TimeSpan.FromSeconds());
  23. Console.WriteLine("The gates are now open for the second time!");
  24. _mainEvent.Set();
  25. Thread.Sleep(TimeSpan.FromSeconds());
  26. Console.WriteLine("The gates have been closed!");
  27. _mainEvent.Reset();
  28. }
  29.  
  30. static void TravelThroughGates(string threadName, int seconds)
  31. {
  32. Console.WriteLine("{0} falls to sleep {1}", threadName, seconds);
  33. Thread.Sleep(TimeSpan.FromSeconds(seconds));
  34. Console.WriteLine("{0} waits for the gates to open!", threadName);
  35. _mainEvent.Wait();
  36. Console.WriteLine("{0} enters the gates!", threadName);
  37. }
  38. /// <summary>
  39. /// ManualResetEventSlim是ManualResetEvent的混合版本,一直保持大门敞开直到手工调用Reset方法,
  40. /// _mainEvent.Set 相当于打开了大门从而允许准备好的线程接收信号并继续工作
  41. /// _mainEvent.Reset 相当于关闭了大门 此时已经准备好执行的信号量 则只能等到下次大门开启时才能够执行
  42. /// </summary>
  43. static ManualResetEventSlim _mainEvent = new ManualResetEventSlim(false);
  44. }
  45. }

Program

C#线程同步--线程通信的更多相关文章

  1. 线程:主线程、子线程 同步线程、异步线程 单线程、多线程 System.Threading与System.Windows.Threading

    入门-------------------------------------------------------------------------------- 概述与概念    一个C#程序开始 ...

  2. 线程同步&线程池

    线程同步&线程池 线程同步 线程不同步会出现的问题: 当多个线程操作同一资源时,会出现重复操作和和操作不存在的资源的问题,为了规避这一问题就需要线程的同步操作来实现资源的共同使用. 线程同步: ...

  3. Python并发编程-线程同步(线程安全)

    Python并发编程-线程同步(线程安全) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 线程同步,线程间协调,通过某种技术,让一个线程访问某些数据时,其它线程不能访问这些数据,直 ...

  4. Win32多线程编程(3) — 线程同步与通信

      一.线程间数据通信 系统从进程的地址空间中分配内存给线程栈使用.新线程与创建它的线程在相同的进程上下文中运行.因此,新线程可以访问进程内核对象的所有句柄.进程中的所有内存以及同一个进程中其他所有线 ...

  5. Qt QThread 线程创建,线程同步,线程通信 实例

    1.  继承QThread, 实现run()方法, 即可创建线程. 2. 实例1 代码 myThread.h #ifndef MYTHREAD_H #define MYTHREAD_H #includ ...

  6. c++线程同步和通信

    一.线程的创建 你也许会说我一直用CreateThread来创建线程,一直都工作得好好的,为什么要用_beginthreadex来代替CreateThread,下面让我来告诉你为什么.    回答一个 ...

  7. Linux多线程编程——多线程与线程同步

    多线程 使用多线程好处: 一.通过为每种事件类型的处理单独分配线程,可以简化处理异步事件的代码,线程处理事件可以采用同步编程模式,启闭异步编程模式简单 二.方便的通信和数据交换 由于进程之间具有独立的 ...

  8. C++线程同步的四种方式(Windows)

    为什么要进行线程同步? 在程序中使用多线程时,一般很少有多个线程能在其生命期内进行完全独立的操作.更多的情况是一些线程进行某些处理操作,而其他的线程必须对其处理结果进行了解.正常情况下对这种处理结果的 ...

  9. 33 - 并发编程-线程同步-Event-lock

    目录 1 线程同步 1.1 Event 1.1.1 什么是Flag? 1.1.2 Event原理 1.1.3 吃包子 1.2 Lock 1.2.1 lock方法 1.2.2 计数器 1.2.3 非阻塞 ...

随机推荐

  1. antd-mobile less文件用模块方式引入

      config-overrides.js文件修改::::: const { injectBabelPlugin, getLoader } = require('react-app-rewired') ...

  2. 记一次Dynamic Batching不生效的爬坑实例分析[Unity]

    最近在项目开发过程中,无意发现游戏场景的绘制占用了大量的Batches,几乎一个模型显示就占用了一个Batch,而Saved by batching数量几乎为0,即没有任何合批渲染优化.这显然跟预期相 ...

  3. vue项目开发时怎么解决跨域

    vue项目中,前端与后台进行数据请求或者提交的时候,如果后台没有设置跨域,前端本地调试代码的时候就会报“No 'Access-Control-Allow-Origin' header is prese ...

  4. selenium webdriver定位不到元素的五种原因及解决办法

    1.动态id定位不到元素 for example:        //WebElement xiexin_element = driver.findElement(By.id("_mail_ ...

  5. ubuntu16.04开机时的.local问题

    开机时显示:您的当前网络有.local域,我们不建议这样做而且这与AVAHI网络服务探测不兼容,该服务已被禁用 解决方法: 在终端输入:sudo gedit /etc/default/avahi-da ...

  6. [Swift]LeetCode909. 蛇梯棋 | Snakes and Ladders

    On an N x N board, the numbers from 1 to N*N are written boustrophedonically starting from the botto ...

  7. Java高级特性-String、StringBuffer和StringBuilder

    Java高级特性-String.StringBuffer和StringBuilder String Java语言中的字符串值都属于String类,虽然有其他方法表示字符串(如字符串数组),但Java一 ...

  8. vue组件的基本使用,以及组件之间的基本传值方式

    组件(页面上的每一个部分都是组件) 1.三部分:结构(template),样式(style),逻辑(script) 2.组件的作用:复用 3.模块包含组件 4.组件创建:     1.全局组件:Vue ...

  9. WebSocket(4)---实现定时推送比特币交易信息

    实现定时推送比特币交易信息 实现功能:跟虚拟币交易所一样,时时更新当前比特币的价格,最高价,最低价,买一价等等...... 提示:(1)本篇博客是在上一遍基础上搭建,上一篇博客地址:[WebSocke ...

  10. 【java设计模式】(3)---代理模式(案例解析)

    设计模式之代理模式 一.概述 1.什么是代理模式? 解释第一遍:代理模式主要由三个元素共同构成: 1)一个接口,接口中的方法是要真正去实现的. 2)被代理类,实现上述接口,这是真正去执行接口中方法的类 ...