当前位置: 首页 > 编程日记 > 正文

改善C#程序的建议6:在线程同步中使用信号量

所谓线程同步,就是多个线程之间在某个对象上执行等待(也可理解为锁定该对象),直到该对象被解除锁定。C#中对象的类型分为引用类型和值类型。CLR在这两种类型上的等待是不一样的。我们可以简单的理解为在CLR中,值类型是不能被锁定的,也即:不能在一个值类型对象上执行等待。而在引用类型上的等待机制,则分为两类:锁定和信号同步。

锁定,使用关键字lock和类型Monitor。两者没有实质区别,前者其实是后者的语法糖。这是最常用的同步技术;

本建议我们讨论的是信号同步。信号同步机制中涉及的类型都继承自抽象类WaitHandle,这些类型有EventWaitHandle(类型化为AutoResetEvent、ManualResetEvent)和Semaphore以及Mutex。见类图6-3:

clip_image002

图 同步功能类类图

EventWaitHandle(子类为AutoResetEvent、ManualResetEvent)和Semaphore以及Mutex都继承自WaitHandle,所以它们底层的原理是一致的,维护的都是一个系统内核句柄。不过我们仍需简单的区分下这三类类型。

EventWaitHandle,维护一个由内核产生的布尔类型对象(我们称之为“阻滞状态”),如果其值为false,那么在它上面等待的线程就阻塞。可以调用类型的Set方法将其值设置为true,解除阻塞。EventWaitHandle类型的两个子类AutoResetEvent和ManualResetEvent,它们的区别并不大,本建议接下来会针对它们阐述如何正确使用信号量。

Semaphore,维护一个由内核产生的整型变量,如果其值为0,则在它上面等待的线程就阻塞,其值大于0,就解除阻塞,同时,每解除阻塞一个线程,其值就减1。

EventWaitHandle和Semaphore提供的都是单应用程序域内的线程同步功能,Mutex则不同,它为我们提供了跨应用程序域阻塞和解除阻塞线程的能力。


1:使用信号机制提供线程同步的一个简单的例子

使用信号机制提供线程同步的一个简单的例子如下:

AutoResetEvent autoResetEvent = new AutoResetEvent(false);

private void buttonStartAThread_Click(object sender, EventArgs e)
{
Thread tWork
= new Thread(() =>
{
label1.Text
= "线程启动..." + Environment.NewLine;
label1.Text
+= "开始处理一些实际的工作" + Environment.NewLine;
//省略工作代码
label1.Text += "我开始等待别的线程给我信号,才愿意继续下去" + Environment.NewLine;
autoResetEvent.WaitOne();
label1.Text
+= "我继续做一些工作,然后结束了!";
//省略工作代码
});
tWork.IsBackground
= true;
tWork.Start();
}

private void buttonSet_Click(object sender, EventArgs e)
{
//给在autoResetEvent上等待的线程一个信号
autoResetEvent.Set();
}

这是一个简单的Winform窗体程序,其中一个按钮负责开启一个新的线程,还有一个按钮负责给刚开启的那个线程发送信号。现在详细解释这里面发生的事情。

AutoResetEvent autoResetEvent = new AutoResetEvent(false);

这段代码创建了一个同步类型对象autoResetEvent,它设置自己的默认阻滞状态是false。这意味着任何在它上面进行等待的线程将会被阻滞。所谓进行等待,就是在线程中应用:

autoResetEvent.WaitOne();

这说明tWork开始在autoResetEvent上等待任何其它地方给它的信号。信号来了,则tWork开始继续工作,否则就一直等着(即阻滞)。接下来我们看到在主线程中(本例中即UI线程,它相对线程tWork来说,就是一个“另外的线程”):

autoResetEvent.Set();

主线程通过上面这句代码负责向在autoResetEvent上等待的线程tWork上下文发送信号,即将tWork的阻滞状态设置为true。tWork接收到这个信号,开始继续工作。

这个例子相当简单,但是已经完整说明了信号机制的工作原理。


2:AutoResetEvent和ManualResetEvent的区别

AutoResetEvent和ManualResetEvent有这样的区别:前者在发送信号完毕后(即调用Set方法),自动将自己的阻滞状态设置为false,而后者需要进行手动设定。可以通过一个例子来说明这种区别:

AutoResetEvent autoResetEvent = new AutoResetEvent(false);

private void buttonStartAThread_Click(object sender, EventArgs e)
{
StartThread1();
StartThread2();
}

private void StartThread1()
{
Thread tWork1
= new Thread(() =>
{
label1.Text
= "线程1启动..." + Environment.NewLine;
label1.Text
+= "开始处理一些实际的工作" + Environment.NewLine;
//省略工作代码
label1.Text += "我开始等待别的线程给我信号,才愿意继续下去" + Environment.NewLine;
autoResetEvent.WaitOne();
label1.Text
+= "我继续做一些工作,然后结束了!";
//省略工作代码
});
tWork1.IsBackground
= true;
tWork1.Start();
}

private void StartThread2()
{
Thread tWork2
= new Thread(() =>
{
label2.Text
= "线程2启动..." + Environment.NewLine;
label2.Text
+= "开始处理一些实际的工作" + Environment.NewLine;
//省略工作代码
label2.Text += "我开始等待别的线程给我信号,才愿意继续下去" + Environment.NewLine;
autoResetEvent.WaitOne();
label2.Text
+= "我继续做一些工作,然后结束了!";
//省略工作代码
});
tWork2.IsBackground
= true;
tWork2.Start();
}

private void buttonSet_Click(object sender, EventArgs e)
{
//给在autoResetEvent上等待的线程一个信号
autoResetEvent.Set();
}

这个例子的本意是要让新起的两个工作线程tWork1和tWork2都阻滞起来,直到收到主线程的信号再继续工作。结果程序运行的结果是,只有一个工作线程继续工作,另外一个工作线程则继续保持阻滞状态。我想原因大家都已经想到了。由于AutoResetEvent在发送信号完毕就在内核中自动将自己的状态设置回false了,所以另外一个工作线程相当于根本没有收到主线程的信号。

要修正这个问题,可以使用ManualResetEvent。大家可以换成ManualResetEvent试一下。


3:应用实例

最后,再举一个需要用到线程同步的实际例子:模拟网络通信。客户端在运行过程中,服务器每隔一段的时间会给客户端发送心跳数据。实际工作中服务器和客户端会是网络中两台不同的终端,在这个例子中我们进行了简化。工作线程tClient模拟客户端,主线程(UI线程)模拟服务器端。客户端每3秒检测是否收到服务器的心跳数据,如果没有心跳数据,则显示网络连接断开。代码如下:

AutoResetEvent autoResetEvent = new AutoResetEvent(false);

private void buttonStartAThread_Click(object sender, EventArgs e)
{
Thread tClient
= new Thread(() =>
{
while (true)
{
//等3秒,3秒没有信号,显示断开
//有信号,则显示更新
bool re = autoResetEvent.WaitOne(3000);
if (re)
{
label1.Text
= string.Format("时间:{0},{1}", DateTime.Now.ToString(), "保持连接状态");
}
else
{
label1.Text
= string.Format("时间:{0},{1}", DateTime.Now.ToString(), "断开,需要重启");
}
}
});
tClient.IsBackground
= true;
tClient.Start();
}

private void buttonSet_Click(object sender, EventArgs e)
{
//模拟发送心跳数据
autoResetEvent.Set();
}


备注:由本问题带来一个Winform跨线程控件赋值和操作的问题。由于在本示例中不影响上面代码的运行,所以没有涉及,但是回复中有人提出来,所以提前简述一下Winform的线程模型:

在Winform框架中,有一个ISynchronizeInvoke接口,所有的UI元素(表现为Control)都继承了该接口。其中,接口中的InvokdRequired属性表示了当前线程是否是创建它的线程。接口中的Invoke和BeginInvoke方法负责将消息发送到消息队列中,这样,UI线程就能够正确处理它。

具体到代码中,对于夸线程控件赋值,可以采用下面的方法:

this.label1.BeginInvoke(new Action(()=>
{
this.label1.Text = "跨线程中赋值";
}));

之前的话题:

改善C#程序的建议5:引用类型赋值为null与加速垃圾回收

改善C#程序的建议4:C#中标准Dispose模式的实现

改善C#程序的建议3:在C#中选择正确的集合进行编码

改善C#程序的建议2:C#中dynamic的正确用法

改善C#程序的建议1:非用ICloneable不可的理由

转载于:https://www.cnblogs.com/luminji/archive/2011/05/03/2034890.html

相关文章:

Jerry眼中的SAP客户数据模型

本文Jerry将介绍八款SAP产品中的客户模型。希望您在阅读完本文之后,能对SAP客户模型设计的思路有一个最最粗浅的了解。 由于Jerry水平和精力所限,本文不会详细阐述这些产品里的客户模型设计细节,而是介绍了一种方法,如果您对这些模…

【spring】spring JDBC开发 、 将创建表生成sql语句的方法

将navicate中已存在表的创建转化成sql语句的方法 1、右击表&#xff0c;选择对象信息 2、点击DDL jar包引入 1、spring-starter-jdbc 代码实现&#xff1a; <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-star…

PL/SQL ——分页编程

通过PL/SQL编程&#xff0c;编写分页存储过程。代码如下所示&#xff1a; 1 --PL/SQL开发编写分页代码 2 --创建包 3 create or replace package Page as 4 type test_cursor is ref cursor 5 end Page; 6 --创建存储过程 7 create or replace procedure Page( 8 (tablenam…

My view towards Machine Learning

Introduction:[to be continued]转载于:https://www.cnblogs.com/JVKing/articles/2290780.html

两个表的更新、表的复制

update 表1 set from 表1&#xff0c;表2 where 条件 表的复制数据 1.select * into newtable from table 2.insert into table select table2 select identity(int,1,1),* into newtable from .. 这条语句有时挺管用的转载于:https://www.cnblogs.com/leshang/archive/2…

Java数据结构简述

1、数组 概念&#xff1a;一个存储元素的线性集合。 数组声明和创建&#xff1a; dataType[] arrayRefVar new dataType[arraySize]; 二维数组&#xff08;多维数组&#xff09;声明和创建&#xff1a; dataType[][] arrayName new dataType[arraylenght1][arraylenght2]; PS…

CORS漏洞利用检测和利用方式

CORS全称Cross-Origin Resource Sharing, 跨域资源共享&#xff0c;是HTML5的一个新特性&#xff0c;已被所有浏览器支持&#xff0c;不同于古老的jsonp只能get请求。 检测方式&#xff1a; 1.curl访问网站   curl https://www.huasec.com -H "Origin: https://test.com…

【spring】具名参数

具名参数 配置xml文件 1、配置dataSource&#xff08;数据源&#xff09; 代码实现&#xff1a; <context:property-placeholderlocation"DB.properties"></context:property-placeholder><bean id"dataSource"class"com.alibaba.d…

利用委托和泛型实现树的常用操作

在日常开发中&#xff0c;经常遇到对树的操作&#xff0c;我们可以利用泛型和委托对这些树进行操作&#xff0c;这样就不需要每有一个树就要实现相应的功能了。 源码在http://files.cnblogs.com/haiconc/LangTest.zip 首先&#xff0c;使用类泛型声明&#xff1a; public class…

Scott的ASP.net MVC框架系列文章之四:处理表单数据(2)

前几周我发表了一系列文章介绍我们正在研究的ASP.NET MVC框架。ASP.NET MVC框架为你提供了一种新的开发Web应用程序的途径&#xff0c;这种途径可以让应用程序变得更加层次清晰&#xff0c;而且更加有利于对代码进行单元测试和支持TDD&#xff08;测试驱动开发&#xff09;开发…

eclipse工作空间配置导出

由于工作与学习的需求&#xff0c;需要使用不同的工作空间。而eclipse的新建工作空间其他以前的配置都没有继承过来&#xff0c;那么就得重新配置一遍。 经过学习其他前辈们的经验与自己的摸索总结一下3种方法&#xff1a; 方法一&#xff1a;使用eclipse的导出功能。 工作目录…

探讨UnsupportedOperationException的原因及解决方案

[原文链接]{https://blog.csdn.net/liu_005/article/details/74091805} 转载于:https://www.cnblogs.com/Wbin01/p/11222483.html

成长轨迹44 【ACM算法之路 百炼poj.grids.cn】【字符串处理】【2799、2976、2975、2742】...

一次ac的就不说啥了。。 2799:浮点数格式 View Code 1 #include <stdio.h> 2 #include <string.h> 3 #include <ctype.h> 4 #include <cmath> 5 6 char flo[10050][55]; 7 int poi[10050]; 8 9 int main()10 {11 int num;12 scanf("%d…

【转载】xmind的使用安装方法

原文链接&#xff1a;https://blog.csdn.net/qq_16093323/article/details/80967867

BCB Access violateion at Address 0000 0003. Read of address 0000 0003

来自网页&#xff1a;&#xff08;我的电脑做不到&#xff09; 运行一个程序&#xff0c;莫名出现一个对话框:access violation at address 0000.. read of address000试了几次问题依旧&#xff0c;网上搜了下解决办法&#xff1a;原文&#xff1a;baidu&#xff0b;google&…

与 Scott Guthrie 一道感受技术激情 1月13日于北京

可能很多朋友已经知道了这个消息&#xff0c;我觉得还是写一下&#xff0c;别让这个机会白白溜走。Scott Guthrie是谁&#xff0c;我就不介绍了&#xff0c;简单说&#xff1a;ASP.NET之父&#xff0c;Silverlight 的主要创始人&#xff0c;还管着太多微软的开发技术和工具&…

【web】将一个jar包更改成war包

可以看到&#xff0c;向tomcat中发布工程刚创建的工程不在可添加的范围内&#xff0c;所以可以看出该工程是一个jar包 1、在pom文件中添加一行代码 代码实现&#xff1a; <artifactId>jar.to.war</artifactId> 2、更新maven工程 此时发现main文件夹下出现了一个w…

牛腩44 整合登陆页 RequiredFieldValidator 和 ValidationSummary 以及 asp.net 自带的MD5 加密...

在我们后台登陆的时候,有 用户名,密码和验证码3个必选项,所以我们托3个验证控件过来 例如这里,如果没有填写用户名,当点提交的时候,显示 红色的 * 号,并且弹出一个 alert 效果如下 这个是怎么做到的呢? 用户名后面的 * 号和弹出的 alert 分别用到 RequiredFieldValida…

HDU-1268 找新朋友 (素数筛选)

找新朋友 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total Submission(s): 2518 Accepted Submission(s): 1183Problem Description新年快到了&#xff0c;“猪头帮协会”准备搞一个聚会&#xff0c;已经知道现有会员N人&#x…

硬盘无法访问文件系统损坏,里面的资料怎样恢复

文件系统损坏说明这个盘的文件系统结构损坏了。在平时如果数据不重要&#xff0c;那么可以直接格式化就能用了。但是有的时候里面的数据很重要&#xff0c;那么就必须先恢复出数据再格式化。具体恢复方法可以看正文了解&#xff08;不格式化的恢复方法&#xff09; 工具/软件&a…

【spring】第二个springmvc helloworld 以及 spring模糊路径

第二个helloword 配置文件&#xff1a; 1、添加pom文件 &#xff08;1&#xff09;配置parent 代码实现&#xff1a; <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version…

jquery倒计时插件可自定义多个倒计时间

jquery倒计时插件设置多个自定义倒计时时间&#xff0c;任意设置天、小时、分钟、秒倒计时间功能。 查看演示>> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">…

怎样用cocos2d-x做一个基于地图块的游戏(Part One)

怎样用cocos2d-X做一个基于地图块的游戏 &#xff08;Part One&#xff09; 在这个分为上下两部分的教程中&#xff0c;我们将介绍如何使用Cocos2D-X和地图编辑器做一款基于地图块的游戏。在这个简单的地图块游戏里&#xff0c;一个精灵将在沙漠里搜寻它可口的西瓜&#xff01;…

【Code Complete】《Code Complete 》

良好编程实践的百科全书&#xff0c;完善编码聚焦于个人技能——所有的内容都来说明我们称之为“编写巧妙的代码”&#xff08;write clean code&#xff0c;clean可以翻译多种意思&#xff0c;只能意会了&#xff0c;有些英语翻译成汉语会很痛苦的&#xff09;。这本书就是那种…

redux示例

? 献上大概只有自己看得懂的学习笔记 结合使用实例理解更容易哦&#xff5e;import React from react; import ReactDom from react-dom; import {createStore} from redux; //解构一个createStore 创建状态对象//默认状态 state const defaultState{arr:[qq,bmw7], };//创建r…

18常用web开发 浮动层、提示层代码下载

1、jQuery顶部固定层页面滚动淡出淡进菜单显示 查看演示>> 2、jQuery鼠标滚动条到页面底部浮动层滑动弹出信息 查看演示>> 3、网页向导Jquery插件wlGuide功能操作步骤引导 查看演示>> 4、jQuery文字段落鼠标悬停图片突出显示和预览 查看演示>> 5、jque…

Android Handler的使用(二)

Handler的使用(二) 一、 Handler与线程的关系 Handler在默认情况下&#xff0c;实际上它和调用它的Activity是处于同一个线程的。 例如在Handler的使用&#xff08;一&#xff09;的示例1中&#xff0c;虽然声明了线程对象&#xff0c;但是在实际调用当中它并没有调用线…

【windows】cmd中的help无法使用的解决方法

今天使用黑框时遇到了一个问题&#xff0c;我在检查tomcat端口是否被占用时发现 黑框中竟然无法识别 netstat&#xff0c;随后试了一下help也无法识别。查了论坛后大名大佬给出的解决方案。 一、首先确定C:\WINDOWS\system32目录下有help.exe这个文件。 二、依次打开右键单击…

对微软Web Deploy的一次艰难调试

2011年初开始做一个项目&#xff0c;开始体验使用微软网站发布工具来发布网站。在服务器端安装发布服务后&#xff0c;可以在Visual Studio界面中右键点击Web项目&#xff0c;再点发布&#xff0c;第一次填好发布设置&#xff0c;以后就可以实现一键发布&#xff0c;虽然还有不…

vue总结 08状态管理vuex

状态管理 类 Flux 状态管理的官方实现 由于状态零散地分布在许多组件和组件之间的交互中&#xff0c;大型应用复杂度也经常逐渐增长。为了解决这个问题&#xff0c;Vue 提供 vuex&#xff1a;我们有受到 Elm 启发的状态管理库。vuex 甚至集成到 vue-devtools&#xff0c;无需配…