转载 信号量 <第六篇>
一、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个东西并无具体联系,各自有各自的作用,但是配合起来使用,威力无穷。
下面用一个例子,结合事件、信号量、线程来实现如下功能:
- 主线程启动辅助线程执行一个长时间任务;
- 辅助线程完成时,触发完成事件(),调用委托,让主线程继续执行;

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的调用方式。
转载 信号量 <第六篇>的更多相关文章
- 信号量 <第六篇>
一.ManualResetEvent 该对象有两种信号量状态True和False.构造函数设置初始状态. WaitOne:该方法用于阻塞线程,默认是无限期的阻塞,支持超时阻塞,如果超时就放弃阻塞,这样 ...
- “MVC+Nhibernate+Jquery-EasyUI” 信息发布系统 第六篇(图片新闻的添加以及带分页的静态页的生成)
“MVC+Nhibernate+Jquery-EasyUI” 信息发布系统 第六篇(图片新闻的添加以及带分页的静态页的生成) 一.这篇文章主要是要实现:图片新闻的添加,无刷新图片的上传,以及添加新闻静 ...
- EnjoyingSoft之Mule ESB开发教程第六篇:Data Transform - 数据转换
目录 1. 数据转换概念 2. 数据智能感知 - DataSense 3. 简单数据转换组件 3.1 Object to JSON 3.2 JSON to XML 3.3 JSON to Object ...
- 小白进阶之Scrapy第六篇Scrapy-Redis详解(转)
Scrapy-Redis 详解 通常我们在一个站站点进行采集的时候,如果是小站的话 我们使用scrapy本身就可以满足. 但是如果在面对一些比较大型的站点的时候,单个scrapy就显得力不从心了. 要 ...
- spring cloud系列教程第六篇-Eureka集群版
spring cloud系列教程第六篇-Eureka集群版 本文主要内容: 本文来源:本文由凯哥Java(kaigejava)发布在博客园博客的.转载请注明 1:Eureka执行步骤理解 2:集群原理 ...
- 解剖SQLSERVER 第十六篇 OrcaMDF RawDatabase --MDF文件的瑞士军刀(译)
解剖SQLSERVER 第十六篇 OrcaMDF RawDatabase --MDF文件的瑞士军刀(译) http://improve.dk/orcamdf-rawdatabase-a-swiss-a ...
- 解剖SQLSERVER 第六篇 对OrcaMDF的系统测试里避免regressions(译)
解剖SQLSERVER 第六篇 对OrcaMDF的系统测试里避免regressions (译) http://improve.dk/avoiding-regressions-in-orcamdf-b ...
- Python之路【第十六篇】:Django【基础篇】
Python之路[第十六篇]:Django[基础篇] Python的WEB框架有Django.Tornado.Flask 等多种,Django相较与其他WEB框架其优势为:大而全,框架本身集成了O ...
- 第六篇 :微信公众平台开发实战Java版之如何自定义微信公众号菜单
我们来了解一下 自定义菜单创建接口: http请求方式:POST(请使用https协议) https://api.weixin.qq.com/cgi-bin/menu/create?access_to ...
随机推荐
- C#编译错误 CS0009:未能打开元数据文件
C#编译错误 CS0009:未能打开元数据文件 编译错误 说明: 在编译向该请求提供服务所需资源的过程中出现错误.请检查下列特定错误详细信息并适当地修改源代码. 编译器错误消息: CS0009: 未能 ...
- Java中构造方法与setter方法
今天在重温Java的同时,一个不是问题的问题,突然地冒出来,不知道大家是不是和我一样,也有过这个比较尴尬的问题 不啰嗦了,那咱就直接说问题吧~~~ 那么首先我们在Java中都会写构造函数,目的是在 ...
- Ocelot中文文档-Not Supported
Not Supported Ocelot不支持以下几种情况 块级编码(Chunked Encoding ) - Ocelot始终会获取消息体的大小并返回内容长度(Content-Length).这种情 ...
- 【Tomcat】Tomcat日志切割
下载并解压缩 cronolog # tar zxvf cronolog-1.6.2.tar.gz 2.进入cronolog安装文件所在目录 # cd cronolog-1.6.2 3.运行安装 # ...
- Rancher2.0构建kubernetes(K8S)集群
一.环境准备 1.准备至少3台CentOS7版本的虚拟机 # IP地址 主机名称 192.168.1.160 rancher 192.168.1.161 master 192.168.1.162 no ...
- Compiler showing 'pi' symbol on error
Question: I was testing some code on Coliru, and I got a strange output. I went down the code and co ...
- canvas学习和滤镜实现
最近学习了 HTML5 中的重头戏--canvas.利用 canvas,前端人员可以很轻松地.进行图像处理.其 API 繁多,这次主要学习常用的 API,并且完成以下两个代码: 实现去色滤镜 实现负色 ...
- D3.js 制作中国地图
from: http://d3.decembercafe.org/pages/map/index.html GeoJSON is a format for encoding a variety of ...
- html/css学习笔记(一)
============================================================================================= 盒子模型 1 ...
- ionic 项目签名
一.ionic 自动签名的好处与坏处(ionic build android/ios) 好处在于:可以直接安装手机上进行安装测试,也可以上传Android或者iOS平台 不好的地方在于:你的电脑环境 ...