线程同步的情景之三

  在情景一情景二中,我分别介绍了当多线程遇到 “资源争用”、“限量使用” 情形时的解决方案,本篇是本系列的最后一种情形,会介绍几种用于解决线程通信的方案。

 情景三:我让你动,你才能动!

  大锤:“老板,拿这个手机让我看看”。

  大锤:“这是手机吗??? 分别就只是一个壳子”。

  老板:“呀,这可能是生产上出了问题,我给你换一个!”

  大锤:“老板,你这是当我是傻子呢?还是傻子呢?还是傻子呢? 这回给我的手机怎么没有电源啊!我要怎么开机啊!”

  万万没想到,经过千挑万选,最终还是找到了一个配件完整的手机。

  老板回去后发现了原因是:生产和上线销售两个环节没有搭配好,当生产的环节还没有结束时,就把中间产物拿去销售了。

  解决办法:所有动作不能擅自执行,必须服从命令,当生产环节完成时会通知上线环节,然后才被允许拿到市场上去销售。

  问题抽象:当某个操作的执行必须依赖于另一个操作的完成时,需要有个机制来保证这种先后关系。

  线程通信方案:ManualResetEventSlim、ManualResetEvent、AutoResetEvent

  方案特性:提供线程通知的能力,没有接到通知前,线程必须等待,有先后顺序。

  各方案间的区别

  在继续阅读前,请确保你已经对用户模式构造、内核模式构造和混合模式构造有所了解,如果尚未了解,建议您先阅读情景一中相关章节。

内核模式(kernal-mode)

  ManualResetEvent 和 AutoResetEvent 都继承自 EventWaitHandle 并最终与 Mutex 和 Semaphore 一样拥有共同的祖宗: WaitHandle。在前面几篇中我有讲过 WaitHandle 是一个抽象类,包装了 Windows 操作系统的内核对象句柄。

  ManualResetEvent: 中文理解就是手动重置事件(这里的事件并不是我们通常意义中按钮的那种事件,更多的应该理解为一个通告)。所有事件的初始状态都为 “不可用”,任何在等待该事件的线程都将一直等待下去。只有当通过 Set 方法释放了一个信号后,等待该事件的线程才被允许执行所需的操作。如果不手动重置,那么状态一直为 “可用”,任何等待该事件的线程可以继续执行操作。只有当调用 Reset 方法后,状态才会变为 “不可用”。通过 WaitOne 来请求状态,从而决定是否执行操作。

  这个特点,有点类似红绿灯,当绿灯亮起,所有机动车都被允许通过,直到再次变成红灯。

  与 Mutex 不同的是,Reset 和 Set 操作可以由不同的线程发起。如:

    ManualResetEvent s = new ManualResetEvent(false);

    Task.Factory.StartNew(() =>
    {
        s.Set();
    });

    Task.Factory.StartNew(() =>
    {
        s.Reset();
    });

    Task.Factory.StartNew(() =>
    {
        s.WaitOne();
    });

  优点:提供线程间通信的能力,可以跨进程使用。

  缺点:速度慢于用户模式、混合模式构造,稍快于 mutex。

  AutoResetEvent: 顾名思义,就是自动重置事件。如果 ManulResetEvent 相当于红绿灯,那 AutoResetEvent 就类似高速入口的闸机,杆抬起一次,通过一辆车。第二辆车要重新等待杆抬起。当调用 Set 后,状态变成 “可用”,但只要一执行 WaitOne 请求状态后,状态即可变成 “不可用”(这个过程不需要 Reset 方法的参与)。

  优点:提供线程间通信的能力,可以跨进程使用。

  缺点:速度慢于用户模式、混合模式构造,稍快于 mutex。

混合模式(hybrid-mode)

  在 .Net 4.0 时候引入了 ManualResetEventSlim 来提高性能。下面是 MSDN 的原话:

在 .NET Framework 4 版中,当等待时间预计非常短时,并且当事件不会跨越进程边界时,可使用 System.Threading.ManualResetEventSlim 类以获得更好的性能。当等待事件变为已发出信号状态的过程中,ManualResetEventSlim 短时间内会使用繁忙旋转。 当等待时间很短时,旋转的开销相对于使用等待句柄来进行等待的开销会少很多。 但是,如果事件在某个时间段内没有变为已发出信号状态,则 ManualResetEventSlim 会采用常规的事件处理等待。

--- 《ManualResetEvent 和 ManualResetEventSlim》

  ManualResetEventSlim 的用法与 ManualResetEvent 几乎相似,只是原先使用 WaitOne 的地方需要使用 Wait 代替。

  优点:提供线程间通信的能力。

  缺点:不能跨进程使用,速度快于内核模式构造。

总 结

  本篇文章所解决的是当两个或多个线程之间需要按某种顺序执行的时候,线程间的同步问题。如果在开发中遇到两个线程需要按某种顺序先后执行的,则应该考虑使用 ManualResetEvent 或 AutoResetEvent。

  本文来自《C# 基础回顾: 线程同步的情景之三

时间: 2024-10-06 03:29:10

线程同步的情景之三的相关文章

线程同步的情景之二

情景一中,我主要介绍了用于解决资源争用时各种方式的区别,本篇文章我们将进一步介绍线程同步的第二种场景. 情景二:数量有限,先到先得   情景简介:与情景一类似,但是这次茅坑的数量不只一个.如果有需求的人数少于茅坑数量,那一切都很和谐.但是人数超过茅坑数量的时候该怎么办?多个人占用一个坑? 解决办法:当所有茅坑都客满的时候,其他人必须乖乖等在外面,只有当有人从里面出来的时候,下一个人才能进去. 问题抽象:当某一资源同一时刻允许一定数量的线程使用的时候,需要有个机制来阻塞多余的线程,直到资源再次变得

线程同步的情景之一

从本篇文章开始,我将陆续介绍多线程中会遇到的三种情况. 情景一:此茅坑有主了 大锤:“我擦,居然一个茅坑有两个人在用.” 大锤:“啊,忍不住了,一起挤挤吧~~~” 叫兽:“舒坦了,先走了.” 叫兽按下了冲水开关.... "哗啦啦....." 大锤:“你妹啊,冲什么水啊,冲得我一身 shit ” 解决方案:为了解决这种混乱的情况,管理员给茅坑加了道门,一次只允许一个人使用,其他人只能在外面等待.而且只要有人占着,就算不拉屎,其他人也只能乖乖排队. 问题抽象:当某一资源可能同时被多个线程读

C# 线程同步的三类情景

C# 已经提供了我们几种非常好用的类库如 BackgroundWorker.Thread.Task等,借助它们,我们就能够分分钟编写出一个多线程的应用程序. 比如这样一个需求:有一个 Winform 窗体,点击按钮后,会将窗体中的数据导出到一个 output.pdf 文件中.原先的代码没有采用多线程技术,所以当点击按钮后,整个窗体就变成无响应了.为了解决这个问题,可以使用 Task.Run(()=>{...导出文件的代码}); 上面的代码看似简单,却隐藏着种种危机.如果在导出的期间,窗体的数据被

11.6 线程同步

11.6.1 互斥Example11.6.2 避免死锁Example11.6.3 pthread_mutex_timedlock 函数Example11.6.4Reader-Writer LocksExample11.6.5 带有超时功能的读写锁11.6.6 条件变量Example11.6.7 自旋锁11.6.8 BarriersExample 当多个线程控制流需要共享内存的时候,我们需要确保每一个线程所看到的数据是一致的.如果一个线程使用别的线程不会读取或者修改的数据,那么一致性问题并不会出现

第二章线程同步基础

Java 7 并发编程实战手册目录 代码下载(https://github.com/Wang-Jun-Chao/java-concurrency) 第二章线程同步基础 2.1简介 多个执行线程共享一个资源的情景,是最常见的并发编程情景之一.在并发应用中常常遇到这样的情景:多个线程读或者写相同的数据,或者访问相同的文件或数据库连接. 为了防止这些共享资源可能出现的错误或数据不一致,我们必须实现一些机制来防止这些错误的发生. 为了解决这些问题,引入了临界区(Critical Section)概念,临

【转】多线程:C#线程同步lock,Monitor,Mutex,同步事件和等待句柄(上)

本篇从Monitor,Mutex,ManualResetEvent,AutoResetEvent,WaitHandler的类关系图开始,希望通过 本篇的介绍能对常见的线程同步方法有一个整体的认识,而对每种方式的使用细节,适用场合不会过多解释.让我们来看看这几个类的关系图: 1.lock关键字      lock是C#关键词,它将语句块标记为临界区,确保当一个线程位于代码的临界区时,另一个线程不进入临界区.如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放.方法是获取给定

JAVA 并发编程-线程同步工具类(十二)

本文主要介绍一些java线程同步工具类,并不进行具体讲解,当有需要时,可以再去结合实例学习. 信号灯(Semaphore) 应用场景举例: 例如公司的打卡系统,如果有一个打卡机,那么一次就只能有一个人打卡,其余的人就被阻塞住,打卡完以后就可由下一个人打卡.如果有3个打卡机,那么一次就允许3个人或者少于三个人打卡,其余的人就得等待打卡机空闲下来才能继续打卡. 结果: 已进入1个线程,还可进入2个 已进入2个线程,还可进入1个 已进入3个线程,还可进入0个 空余出1个 已进入4个线程,还可进入0个

[.net]基元线程同步构造

1 /* 基元线程同步构造 2 用户模式构造: 3 易变构造(Volatile Construct) 4 互锁构造(Interlocked Construct):自旋锁(Spinlock) 乐观锁(Optimistic Concurrency Control,乐观并发控制) 5 内核模式构造: 6 事件构造(Event) 7 信号量构造(Semaphore) 8 互斥体构造(Mutex) 9 */ 10 11 //易变构造,Volatile.Write()之前的所有字段写入操作,必须再该方法调用

iOS多线程编程:线程同步总结 NSCondtion

1:原子操作 - OSAtomic系列函数 iOS平台下的原子操作函数都以OSAtomic开头,使用时需要包含头文件<libkern/OSBase.h>.不同线程如果通过原子操作函数对同一变量进行操作,可以保证一个线程的操作不会影响到其他线程内对此变量的操作,因为这些操作都是原子式的.因为原子操作只能对内置类型进行操作,所以原子操作能够同步的线程只能位于同一个进程的地址空间内. 2:锁 - NSLock系列对象 iOS平台下的锁对象为NSLock对象,进入锁通过调用lock函数,解锁调用unl