zoukankan      html  css  js  c++  java
  • C#多线程:深入了解线程同步lock,Monitor,Mutex,同步事件和等待句柄(中)

    本篇继续介绍WaitHandler类及其子类 Mutex,ManualResetEvent,AutoResetEvent的用法。.NET中线程同步的方式多的让人看了眼花缭乱,究竟该怎么去理解呢?其实,我们抛开.NET环境看线程同步,无非是执行两种操作:一是互斥/加锁,目的是保证临界区代码操作的“原子性”;另一种是信号灯操作,目的是保证多个线程按照一定顺序执行,如生产者线程要先于消费者线程执行。.NET中线程同步的类无非是对这两种方式的封装,目的归根结底都可以归结为实现互斥/ 加锁或者是信号灯这两种方式,只是它们的适用场合有所不。下面我们根据类的层次结构了解WaitHandler及其子类。 
          1.WaitHandler 

          WaitHandle是Mutex,Semaphore,EventWaitHandler,AutoResetEvent,ManualResetEvent共同的祖先,它封装Win32同步句柄内核对象,也就是说是这些内核对象的托管版本。 

          线程可以通过调用WaitHandler实例的方法WaitOne在单个等待句柄上阻止。此外,WaitHandler类重载了静态方法,以等待所有指定的等待句柄都已收集到信号WaitAll,或者等待某一指定的等待句柄收集到信号WaitAny。这些方法都提供了放弃等待的超时间隔、在进入等待之前退出同步上下文的机会,并允许其它线程使用同步上下文。WaitHandler是C#中的抽象类,不能实例化。 

          2.EventWaitHandler vs. ManualResetEvent vs. AutoResetEvent(同步事件) 

          我们先看看两个子类ManualResetEvent和AutoResetEvent在.NET Framework中的实现: 

    C#代码  收藏代码
    1. //.NET Framework中ManualResetEvent类的实现  
    2.     [ComVisible(true), HostProtection(SecurityAction.LinkDemand, Synchronization = true, ExternalThreading = true)]  
    3.     public sealed class ManualResetEvent : EventWaitHandle  
    4.     {  
    5.         // Methods  
    6.         public ManualResetEvent(bool initialState) : base(initialState, EventResetMode.ManualReset)  
    7.         {  
    8.         }  
    9.     }  
    10.   
    11.     //.NET Framework中AutoResetEvent类的实现  
    12.     [ComVisible(true), HostProtection(SecurityAction.LinkDemand, Synchronization = true, ExternalThreading = true)]  
    13.     public sealed class AutoResetEvent : EventWaitHandle  
    14.     {  
    15.         // Methods  
    16.         public AutoResetEvent(bool initialState)  
    17.             : base(initialState, EventResetMode.AutoReset)  
    18.         {  
    19.         }  
    20.     }  


      原来ManualResetEvent和AutoResetEvent都继承自EventWaitHandler,它们的唯一区别就在于父类 EventWaitHandler的构造函数参数EventResetMode不同,这样我们只要弄清了参数EventResetMode值不同时,EventWaitHandler类控制线程同步的行为有什么不同,两个子类也就清楚了。为了便于描述,我们不去介绍父类的两种模式,而直接介绍子类。 

          ManualResetEvent和AutoResetEvent的共同点: 
          1)Set方法将事件状态设置为终止状态,允许一个或多个等待线程继续;Reset方法将事件状态设置为非终止状态,导致线程阻止;WaitOne阻止当前线程,直到当前线程的WaitHandler收到事件信号。 
          2)可以通过构造函数的参数值来决定其初始状态,若为true则事件为终止状态从而使线程为非阻塞状态,为false则线程为阻塞状态。 
          3)如果某个线程调用WaitOne方法,则当事件状态为终止状态时,该线程会得到信号,继续向下执行。 

          ManualResetEvent和AutoResetEvent的不同点: 
          1)AutoResetEvent.WaitOne()每次只允许一个线程进入,当某个线程得到信号后,AutoResetEvent会自动又将信号置为不发送状态,则其他调用WaitOne的线程只有继续等待,也就是说AutoResetEvent一次只唤醒一个线程; 
          2)ManualResetEvent则可以唤醒多个线程,因为当某个线程调用了ManualResetEvent.Set()方法后,其他调用WaitOne的线程获得信号得以继续执行,而ManualResetEvent不会自动将信号置为不发送。 
          3)也就是说,除非手工调用了ManualResetEvent.Reset()方法,则ManualResetEvent将一直保持有信号状态,ManualResetEvent也就可以同时唤醒多个线程继续执行。 

          示例场景:张三、李四两个好朋友去餐馆吃饭,两个人点了一份宫爆鸡丁,宫爆鸡丁做好需要一段时间,张三、李四不愿傻等,都专心致志的玩起了手机游戏,心想宫爆鸡丁做好了,服务员肯定会叫我们的。服务员上菜之后,张三李四开始享用美味的饭菜,饭菜吃光了,他们再叫服务员过来买单。我们可以从这个场景中抽象出来三个线程,张三线程、李四线程和服务员线程,他们之间需要同步:服务员上菜—>张三、李四开始享用宫爆鸡丁—>吃好后叫服务员过来买单。这个同步用什么呢? ManualResetEvent还是AutoResetEvent?通过上面的分析不难看出,我们应该用ManualResetEvent进行同步,下面是程序代码: 

    张三李四吃饭的故事 

    C#代码  收藏代码
    1. public class EventWaitTest  
    2.     {  
    3.         private string name; //顾客姓名  
    4.         //private static AutoResetEvent eventWait = new AutoResetEvent(false);  
    5.         private static ManualResetEvent eventWait = new ManualResetEvent(false);  
    6.         private static ManualResetEvent eventOver = new ManualResetEvent(false);  
    7.   
    8.         public EventWaitTest(string name)  
    9.         {  
    10.             this.name = name;  
    11.         }  
    12.   
    13.         public static void Product()  
    14.         {  
    15.             Console.WriteLine("服务员:厨师在做菜呢,两位稍等");  
    16.             Thread.Sleep(2000);  
    17.             Console.WriteLine("服务员:宫爆鸡丁好了");  
    18.             eventWait.Set();  
    19.             while (true)  
    20.             {  
    21.                 if (eventOver.WaitOne(1000, false))  
    22.                 {  
    23.                     Console.WriteLine("服务员:两位请买单");  
    24.                     eventOver.Reset();  
    25.                 }  
    26.             }  
    27.         }  
    28.   
    29.         public void Consume()  
    30.         {  
    31.             while (true)  
    32.             {  
    33.                 if (eventWait.WaitOne(1000, false))  
    34.                 {  
    35.                     Console.WriteLine(this.name + ":开始吃宫爆鸡丁");  
    36.                     Thread.Sleep(2000);  
    37.                     Console.WriteLine(this.name + ":宫爆鸡丁吃光了");  
    38.                     eventWait.Reset();  
    39.                     eventOver.Set();  
    40.                     break;  
    41.                 }  
    42.                 else  
    43.                 {  
    44.                     Console.WriteLine(this.name + ":等着上菜无聊先玩会手机游戏");  
    45.                 }  
    46.             }  
    47.         }  
    48.     }  
    49.   
    50.     public class App  
    51.     {  
    52.         public static void Main(string[] args)  
    53.         {  
    54.             EventWaitTest zhangsan = new EventWaitTest("张三");  
    55.             EventWaitTest lisi = new EventWaitTest("李四");  
    56.   
    57.             Thread t1 = new Thread(new ThreadStart(zhangsan.Consume));  
    58.             Thread t2 = new Thread(new ThreadStart(lisi.Consume));  
    59.             Thread t3 = new Thread(new ThreadStart(EventWaitTest.Product));  
    60.   
    61.             t1.Start();  
    62.             t2.Start();  
    63.             t3.Start();  
    64.   
    65.             Console.Read();           
    66.         }  
    67.     }  


    编译后查看运行结果,符合我们的预期,控制台输出为: 
          服务员:厨师在做菜呢,两位稍等... 
          张三:等着上菜无聊先玩会手机游戏 
          李四:等着上菜无聊先玩会手机游戏 
          张三:等着上菜无聊先玩会手机游戏 
          李四:等着上菜无聊先玩会手机游戏 
          服务员:宫爆鸡丁好了 
          张三:开始吃宫爆鸡丁 
          李四:开始吃宫爆鸡丁 
          张三:宫爆鸡丁吃光了 
          李四:宫爆鸡丁吃光了 
          服务员:两位请买单 

          如果改用AutoResetEvent进行同步呢?会出现什么样的结果?恐怕张三和李四就要打起来了,一个享用了美味的宫爆鸡丁,另一个到要付账的时候却还在玩游戏。感兴趣的朋友可以把注释的那行代码注释去掉,并把下面一行代码注释掉,运行程序看会出现怎样的结果。 

           3.Mutex(互斥体) 

           Mutex和EventWaitHandler有着共同的父类WaitHandler类,它们同步的函数用法也差不多,这里不再赘述。Mutex的突出特点是可以跨应用程序域边界对资源进行独占访问,即可以用于同步不同进程中的线程,这种功能当然这是以牺牲更多的系统资源为代价的。 

          这种跨进程同步的一种应用是,限制同一台电脑中同时打开两个相同的程序。具体实现可以参考《用Mutex或进程限制用户在一台电脑上同时打开两个程序》。 

  • 相关阅读:
    122. Best Time to Buy and Sell Stock II
    121. Best Time to Buy and Sell Stock
    72. Edit Distance
    583. Delete Operation for Two Strings
    582. Kill Process
    indexDB基本用法
    浏览器的渲染原理
    js实现txt/excel文件下载
    git 常用命令
    nginx进入 配置目录时
  • 原文地址:https://www.cnblogs.com/lizhenlin/p/6227857.html
Copyright © 2011-2022 走看看