C#中线程同步对象的方法分析

本文实例讲述了C#中线程同步对象的方法。分享给大家供大家参考。具体分析如下:

在编写多线程程序时无可避免会遇到线程的同步问题。什么是线程的同步呢?

举个例子:如果在一个公司里面有一个变量记录某人T的工资count=100,有两个主管A和B(即工作线程)在早一些时候拿了这个变量的值回去,过了一段时间A主管将T的工资加了5块,并存回count变量,而B主管将T的工资减去3块,并存回count变量。好了,本来T君可以得到102块的工资的,现在就变成98块了。这就是线程同步要解决的问题。

在.Net的某些对象里面,在读取里面的数据的同时还可以修改数据,这类的对象就是“线程安全”。但对于自己编写的代码段而言,就必须使用线程同步技术来保证数据的完整性和正确性了。

有几个规律:

1、如果一个对象(或变量)不会同时被多个其他线程访问,那么这个对象是不需使用线程同步的。
2、如果虽然有多个线程同时访问一个对象,但他们所访问的数据或方法并不相同(不交叉),那这种情况也不需使用线程同步。
例如上例中的那个公司里面如果有 T 和 Q 两个人,但他们的工资分别是由 A 和 B 主管的,那么这个工资的处理就不需要线程同步了。
3、如果一个对象会同时被多个其他线程访问,一般只需为这个对象添加线程同步的代码,而其他线程是不需添加额外代码的。

在C#里面用于实现线程同步的常用类有如下几类

1、Mutex类(互斥器),Monitor类,lock方法
2、ManualResetEvent类,AutoResetEvent类(这两个都是由EventWaitHandle类派生出来的)
3、ReaderWriterLock类

同一类的作用都差不多:其中第一类的作用是:用来保护某段代码在执行的时候以独占的方式执行,这时如果有第二个线程想访问这个对象时就会被暂停。一直等到独占的代码执行为止。就好比一堆人同时上一个公共厕所一样,使用这个方法就可以解决文章一开始时提出的问题:主管A要处理T君的工资之前,先lock一下T君,然后取出目前的count值,处理完之后再解除T君的锁定。如果主管B在主管A处理工资时也想取出count值,那么它只能是一直地等待A处理完之后才能继续。使用这个方法的一个缺点就是会降低程序的效率。本来是一个多个线程的操作,一旦遇到lock的语句时,那么这些线程只要排队处理,形同一个单线程操作。

下面举个例子说明一下这三个方法的使用:

假定有一个Tools类,里面一个int变量,还有Add和Delete方法,其中Add方法会使int变量的值增加,Delete方法使int变量值减少:

代码如下:

public class Tools
{
private int count = 100;
public void Add(int n)
{
count+=n;
}
public void Delete(int n)
{
count-=n;
}
}

在多个线程同时访问这段代码时,因为一个语句会被编译器编译成多个指令,所以会可能出现这种情况:但某个线程调用Add方法时,这时的count值为 100,而正当要加上n的时候,另外一个线程调用了Delete,它要减去m,结果count加上了n,然后又在原先count=100的值的情况
下减掉了m,最后的结果是count被减去了m,而没有加上n。很明显Add方法和Delete方法是不能同时被调用的,所以必须进行线程同步处理。简单的方法是用lock语句:

代码如下:

public class Tools
{
private object abcde = new object();
private int count = 100;
public void Add(int n)
{
lock(abcde)
{
count+=n;
}
}
public void Delete(int n)
{
lock(abcde)
{
count-=n;
}
}
}

其中abcde是一个private级的内部变量,它不表示任何的意义,只是作为一种“令牌”的角色。

当执行Add方法中的lock(abcde)方法时,这个令牌就在Add方法的手中了,如果这时有第二个线程也想拿这个令牌,没门,惟有等待。一旦第一个lock语句的花括号范围结束之后,这时令牌就被释放了,同时会迅速落到第二个线程的手中,并且排除其他后来的人。

使用Monitor类的方法大致一样:

代码如下:

public class Tools
{
private object abcde = new object();
private int count = 100;
public void Add(int n)
{
Monitor.Enter(abcde);
count+=n;
Monitor.Exit(abcde);
}
public void Delete(int n)
{
Monitor.Enter(abcde);
count-=n;
Monitor.Exit(abcde);
}
}

Monitor的常用方法:Enter和Exit都是静态方法,作用跟lock语句的两个花括号一样。
而使用 Mutex 就不需声明一个“令牌”对象了,但要实例化之后才可以使用:

代码如下:

public class Tools
{
private Mutex mut = new Mutex();
private int count = 100;
public void Add(int n)
{
mut.WaitOne();
count+=n;
mut.ReleaseMutex();
}
public void Delete(int n)
{
mut.WaitOne();
count-=n;
mut.ReleaseMutex();
}
}

其中的WaitOne为等待方法,一直等到Mutex 被释放为止。初始的情况下,Mutex 对象是处于释放状态的,而一旦执行了WaitOne方法之后,它就被捕获了,一直到被调用了ReleaseMutex方法之后才被释放。
使用这三种方法都有一个要注意的问题,就是在独占代码段里面如果引起了异常,可能会使“令牌”对象不被释放,这样程序就会一直地死等下去了。
所以要在独占代码段里面处理好异常。例如下面这样的代码就是错误的:

代码如下:

public void Add(int n)
{
try
{
mut.WaitOne();
count+=n;
//....这里省略了N行代码
//....这里是有可能引起异常的代码
//....这里省略了N行代码
mut.ReleaseMutex();
}
catch
{
Console.Writeline("error.");
}
}

上面的代码一旦在try和catch里面发生了异常,那么Mutex就不能被释放,后面的程序就会卡死在WaitOne()一行,而应该改成这样:

代码如下:

public void Add(int n)
{
mut.WaitOne();
try
{
count+=n;
//....这里省略了N行代码
//....这里是有可能引起异常的代码
//....这里省略了N行代码
}
catch
{
Console.Writeline("error.");
}
mut.ReleaseMutex();
}

现在谈一下第二种:
ManualResetEvent类,AutoResetEvent类
上面这两个类都是由EventWaitHandle类派生出来的,所以功能和调用方法都很相似。
这两个类常用于阻断某个线程的执行,然后在符合条件的情况下再恢复其执行。
举个例子,你想送花给一个MM,托了一个送花的小伙子送了过去,而你希望当MM收到花之后就立即打个电话过去告诉她。
但问题是你不知道花什么时候才送到MM的手里,打早了打迟了都不好,这时你可以使用ManualResetEvent对象帮忙。当委托小伙子送花过去的时候,使用ManualResetEvent的WaitOne方法进行等待。当小伙子把花送到MM的手中时,再调用一下ManualResetEvent的Set方法,你就可以准时地打电话过去了。
另外ManualResetEvent还有一个Reset方法,用来重新阻断调用者执行的,情况就好比你委托了这个小伙子送花给N个MM,而又想准时地给这N个MM打电话的情况一样。

代码如下:

using System;
using System.Threading;
public class TestMain
{
private static ManualResetEvent ent = new ManualResetEvent(false);
public static void Main()
{
Boy sender = new Boy(ent);
Thread th = new Thread(new ThreadStart(sender.SendFlower));
th.Start();
ent.WaitOne(); //等待工作
Console.WriteLine("收到了吧,花是我送嘀:)");
Console.ReadLine();
}
}
public class Boy
{
ManualResetEvent ent;
public Boy(ManualResetEvent e)
{
ent = e;
}
public void SendFlower()
{
Console.WriteLine("正在送花的途中");
for (int i = 0; i < 10; i++)
{
Thread.Sleep(200);
Console.Write("..");
}
Console.WriteLine(" 花已经送到MM手中了,boss");
ent.Set(); //通知阻塞程序
}
}

而AutoResetEvent类故名思意,就是在每次Set完之后自动Reset。让执行程序重新进入阻塞状态。
即AutoResetEvent.Set() 相当于 ManualResetEvent.Set() 之后又立即 ManualResetEvent.Reset(),其他的就没有什么不同的了。
举个送花给N个MM的例子:

代码如下:

using System;
using System.Threading;
public class TestMain
{
private static AutoResetEvent ent = new AutoResetEvent(false);
public static void Main()
{
Boy sender = new Boy(ent);
for (int i = 0; i < 3; i++)
{
Thread th = new Thread(new ThreadStart(sender.SendFlower));
th.Start();
ent.WaitOne(); //等待工作
Console.WriteLine("收到了吧,花是我送嘀:) ");
}
Console.ReadLine();
}
}
public class Boy
{
AutoResetEvent ent;
public Boy(AutoResetEvent e)
{
ent = e;
}
public void SendFlower()
{
Console.WriteLine("正在送花的途中");
for (int i = 0; i < 10; i++)
{
Thread.Sleep(200);
Console.Write("..");
}
Console.WriteLine(" 花已经送到MM手中了,boss");
ent.Set(); //通知阻塞程序,这里的效果相当于 ManualResetEvent的Set()方法+Reset()方法
}
}

希望本文所述对大家的C#程序设计有所帮助。

(0)

相关推荐

  • C# 线程同步详解

    前言 当线程池的线程阻塞时,线程池会创建额外的线程,而创建.销毁和调度线程所需要相当昂贵的内存资源,另外,很多的开发人员看见自己程序的线程没有做任何有用的事情时习惯创建更多的线程,为了构建可伸缩.响应灵敏的程序,我们在前面介绍了C#异步编程详解 但是异步编程同样也存在着很严重的问题,如果两个不同的线程访问相同的变量和数据,按照我们异步函数的实现方式,不可能存在两个线程同时访问相同的数据,这个时候我们就需要线程同步.多个线程同时访问共享数据的时,线程同步能防止数据损坏,之所以强调同时这个概念,因为

  • C#简单多线程同步和优先权用法实例

    本文实例讲述了C#简单多线程同步和优先权用法.分享给大家供大家参考.具体分析如下: 本文实例题目如下: 麦当劳有两个做汉堡的厨师(工号:11,12)和三个销售人员(工号:21,22,23). 厨师生产汉堡,并负责将做好的汉堡放入货架,货架台大小有限,最多放6个汉堡,11和12不能同时往货架台上放汉堡,11具有优先权. 销售人员负责销售食品,三个销售人员取食品时,货架不能为空,三人不能同时取,23优先权最高,21最低.21卖的最快,取得频率最高,22次之. 一天的工作量是销售70个汉堡. 这里先来

  • C#线程同步的三类情景分析

    本文实例讲述了C#线程同步的三类情景,分享给大家供大家参考.具体分析如下: C# 已经提供了我们几种非常好用的类库如 BackgroundWorker.Thread.Task等,借助它们,我们就能够分分钟编写出一个多线程的应用程序. 比如这样一个需求:有一个 Winform 窗体,点击按钮后,会将窗体中的数据导出到一个 output.pdf 文件中.原先的代码没有采用多线程技术,所以当点击按钮后,整个窗体就变成无响应了.为了解决这个问题,可以使用 Task.Run(()=>{...导出文件的代码

  • 详细解析C#多线程同步事件及等待句柄

    最近捣鼓了一下多线程的同步问题,发现其实C#关于多线程同步事件处理还是很灵活,这里主要写一下,自己测试的一些代码,涉及到了AutoResetEvent 和 ManualResetEvent,当然还有也简要提了一下System.Threading.WaitHandle.WaitOne .System.Threading.WaitHandle.WaitAny和System.Threading.WaitHandle.WaitAll ,下面我们一最初学者的角度来看,多线程之间的同步. 假设有这样的一个场

  • c#线程同步使用详解示例

    在应用程序中使用多个线程的一个好处是每个线程都可以异步执行.对于 Windows 应用程序,耗时的任务可以在后台执行,而使应用程序窗口和控件保持响应.对于服务器应用程序,多线程处理提供了用不同线程处理每个传入请求的能力.否则,在完全满足前一个请求之前,将无法处理每个新请求.然而,线程的异步特性意味着必须协调对资源(如文件句柄.网络连接和内存)的访问.否则,两个或更多的线程可能在同一时间访问相同的资源,而每个线程都不知道其他线程的操作. 线程同步的方式 线程同步有:临界区.互斥区.事件.信号量四种

  • 详解C#多线程之线程同步

    多线程内容大致分两部分,其一是异步操作,可通过专用,线程池,Task,Parallel,PLINQ等,而这里又涉及工作线程与IO线程:其二是线程同步问题,鄙人现在学习与探究的是线程同步问题. 通过学习<CLR via C#>里面的内容,对线程同步形成了脉络较清晰的体系结构,在多线程中实现线程同步的是线程同步构造,这个构造分两大类,一个是基元构造,一个是混合构造.所谓基元则是在代码中使用最简单的构造.基原构造又分成两类,一个是用户模式,另一个是内核模式.而混合构造则是在内部会使用基元构造的用户模

  • C#中实现线程同步lock关键字的用法详解

    1. lock关键字保证一个代码块在执行的过程中不会受到其他线程的干扰,这是通过在该代码块的运行过程中对特定的对象加互斥锁来实现的. 2. lock关键字的参数必须是引用类型的对象.lock对基本数据类型如int,long等无效,因为它所作用的类型必须是对象.如果传入long类型数据,势必被转换为Int64结构类型,则加锁的是全新的对象引用.如果需要对它们进行互斥访问限制,可以使用System.Threading.Interlocked类提供的方法,这个类是提供原子操作的. 3. lock(th

  • c#.net多线程编程教学——线程同步

    随着对多线程学习的深入,你可能觉得需要了解一些有关线程共享资源的问题. .NET framework提供了很多的类和数据类型来控制对共享资源的访问. 考虑一种我们经常遇到的情况:有一些全局变量和共享的类变量,我们需要从不同的线程来更新它们,可以通过使用System.Threading.Interlocked类完成这样的任务,它提供了原子的,非模块化的整数更新操作. 还有你可以使用System.Threading.Monitor类锁定对象的方法的一段代码,使其暂时不能被别的线程访问. System

  • C#中线程同步对象的方法分析

    本文实例讲述了C#中线程同步对象的方法.分享给大家供大家参考.具体分析如下: 在编写多线程程序时无可避免会遇到线程的同步问题.什么是线程的同步呢? 举个例子:如果在一个公司里面有一个变量记录某人T的工资count=100,有两个主管A和B(即工作线程)在早一些时候拿了这个变量的值回去,过了一段时间A主管将T的工资加了5块,并存回count变量,而B主管将T的工资减去3块,并存回count变量.好了,本来T君可以得到102块的工资的,现在就变成98块了.这就是线程同步要解决的问题. 在.Net的某

  • Android开发中Intent传递对象的方法分析

    本文实例分析了Android开发中Intent传递对象的方法.分享给大家供大家参考,具体如下: 方法一: 利用方法:public Intent putExtra (String name, Parcelable value)传递一个Parceable的参数,此方法的参数被序列化到内存. 利用方法:public Intent putExtra (String name, Serializable value)传递一个实现了序列化接口类的对象,此方法的实参被序列化到磁盘. 方法二: 把数据存放到应用

  • 详解处理Java中的大对象的方法

    目录 String中的substring 集合大对象扩容 保持合适的对象粒度 Bitmap 把对象变小 数据的冷热分离 数据双写 写入 MQ 分发 使用 Binlog 同步 思维发散 小结 本文我们将讲解一下对于“大对象”的优化.这里的“大对象”,是一个泛化概念,它可能存放在 JVM 中,也可能正在网络上传输,也可能存在于数据库中. 那么为什么大对象会影响我们的应用性能呢? 第一,大对象占用的资源多,垃圾回收器要花一部分精力去对它进行回收: 第二,大对象在不同的设备之间交换,会耗费网络流量,以及

  • JavaScript 中有关数组对象的方法(详解)

    JS 处理数组多种方法 js 中的数据类型分为两大类:原始类型和对象类型. 原始类型包括:数值.字符串.布尔值.null.undefined 对象类型包括:对象即是属性的集合,当然这里又两个特殊的对象----函数(js中的一等对象).数组(键值的有序集合). 数组元素的添加 arrayObj.push([item1 [item2 [. . . [itemN ]]]]); 将一个或多个新元素添加到数组结尾,并返回数组新长度 arrayObj.unshift([item1 [item2 [. . .

  • jQuery插件EasyUI获取当前Tab中iframe窗体对象的方法

    本文实例讲述了jQuery插件EasyUI获取当前Tab中iframe窗体对象的方法.分享给大家供大家参考,具体如下: 我们在使用EasyUI Tabs框架时,在框架最顶层的弹出窗体中需要操作当前Tab的iframe窗体内容或方法,这时候我们就可以使用以下方法来实现. 具体实现代码如下所示: function getTabWindow() { var curTabWin = null; var curTab = parent.$('#main-center').tabs('getSelected

  • js接收并转化Java中的数组对象的方法

    在做项目时,要向ocx控件下发命令,就要在js中得到java中的对象,然后拼成一种格式,下发下去...当对象是一个时比较简单,但如果对象是一个数组时,就略显麻烦了. 开始我以为有简单的方式,可以直接进行内容的转化,后来发现不可以,网上说js与java没有桥接的东西,所以呢: 我的解决方案是:在action层,将java的对象数组转化为Json串,而在js中,再把json转化为数组对象. 1.将java的对象数组转化为Json串: 要用到两个类: net.sf.json.JSONObject ne

  • js中遍历Map对象的方法

    js中遍历Map对象的方法 console.log(jsonData);//火狐控制台打印输出: Object { fileNumber="文件编号", fileName="文件名称"} for(var key in jsonData) console.log("属性:" + key + ",值:"+ jsonData[key]); } 打印输出: 属性:fileNumber, 值:文件编号 属性:fileName, 值:文件

  • JavaScript中访问节点对象的方法有哪些如何使用

    JavaScript中访问节点对象的方法有哪些? 复制代码 代码如下: var obj = document.getElementById('fdafda'); var obj = document.f1; obj.method="post"; obj.action='Bb'; var obj = document.f1.userName; var obj = document.forms[0].userName; var obj = document.forms[0].element

  • 浅谈JavaScript 中有关时间对象的方法

    ECMAScript中的Date类型是在早期 Java 中的 Java.unile.Date 类基础上构建的.为此 Date 类型使用自 UTC (Coordinated Universal Time, 国际协调时间)1970年1月1日午夜开始经过的毫秒数来保存日期. 在这种数据存储格式下, Date 类型保存的日期能够精确到1970年1月1日之前或之后的285616年 •Date.parse(string) 接受一个表示日期的字符串参数,然后尝试根据这个字符串返回相应日期的毫秒数,不能转换则返

  • 微信小程序使用setData修改数组中单个对象的方法分析

    本文实例讲述了微信小程序使用setData修改数组中单个对象的方法.分享给大家供大家参考,具体如下: 微信小程序已经出来挺久的时间了,之前只是在文档上粗略的看了一下,最近稍得空闲,便利用微信小程序平台写一个练手的项目,顺便学习一下小程序开发,感觉大体跟前端开发基本类似,但是因为是在微信的平台上运行,具体还是要根据小程序的规则来编写代码的,其中还是有部分的内容跟前端中常见的有所不同,于是接下来的博客里,也会顺手记录微信小程序开发过程中的一些坑,帮助后来的小程序开发者少踩一些坑,当然我踩的坑大部分都

随机推荐