已阅读5页,还剩8页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
C# 实现多线程的同步方法详解 本文主要描述在C#中线程同步的方法。线程的基本概念网上资料也很多就不再赘述了。直接接入 主题,在多线程开发的应用中,线程同步是不可避免的。在.Net框架中,实现线程同步主要通过以下的几种方式来实现,在MSDN的线程指南中已经讲了几 种,本文结合作者实际中用到的方式一起说明一下。 1. 维护自由锁(InterLocked)实现同步 2. 监视器(Monitor)和互斥锁(lock) 3. 读写锁(ReadWriteLock) 4. 系统内核对象 1) 互斥(Mutex), 信号量(Semaphore), 事件(AutoResetEvent/ManualResetEvent) 2) 线程池 除了以上的这些对象之外实现线程同步的还可以使用Thread.Join方法。这种方法比较简单,当你在第一个线程运行时想等待第二个线程执行结果,那么你可以让第二个线程Join进来就可以了。 自由锁(InterLocked) 对一个32位的整型数进行递增和递减操作来实现锁,有人会问为什么不用+或-来 操作。因为在多线程中对锁进行操作必须是原子的,而+和-不具备这个能力。InterLocked类还提供了两个另外的函数Exchange, CompareExchange用于实现交换和比较交换。Exchange操作会将新值设置到变量中并返回变量的原来值: int oVal = InterLocked.Exchange(ref val, 1)。 监视器(Monitor) 在MSDN中对Monitor的描述是: Monitor 类通过向单个线程授予对象锁来控制对对象的访问。 Monitor类是一个静态类因此你不能通过实例化来得到类的对象。Monitor 的成员可以查看MSDN,基本上Monitor的效果和lock是一样的,通过加锁操作Enter设置临界区,完成操作后使用Exit操作来释放对象锁。 不过相对来说Monitor的功能更强,Moniter可以进行测试锁的状态,因此你可以控制对临界区的访问选择,等待or离开, 而且Monitor还可以在释放锁之前通知指定的对象,更重要的是使用Monitor可以跨越方法来操作。Monitor提供的方法很少就只有获取锁的方 法Enter, TryEnter;释放锁的方法Wait, Exit;还有消息通知方法Pulse, PulseAll。经典的Monitor操作是这样的: / 通监视器来创建临界区 static public void DelUser(string name) try / 等待线程进入 Monitor.Enter(Names); Names.Remove(name); Console.WriteLine(Del: 0, Names.Count); Monitor.Pulse(Names); finally / 释放对象锁 Monitor.Exit(Names); 其中Names是一个List, 这里有一个小技巧,如果你想声明整个方法为线程同步可以使用方法属性: / 通过属性设置整个方法为临界区 MethodImpl(MethodImplOptions.Synchronized) static public void AddUser(string name) Names.Add(name); Console.WriteLine(Add: 0,Names.Count); 对于Monitor的使用有一个方法是比较诡异的,那就是Wait方法。在MSDN中对Wait的描述是: 释放对象上的锁以便允许其他线程锁定和访问该对象。 这里提到的是先释放锁,那么显然我们需要先得到锁,否则调用Wait会出现异常,所 以我们必须在Wait前面调用Enter方法或其他获取锁的方法,如lock,这点很重要。对应Enter方法,Monitor给出来另一种实现 TryEnter。这两种方法的主要区别在于是否阻塞当前线程,Enter方法在获取不到锁时,会阻塞当前线程直到得到锁。不过缺点是如果永远得不到锁那 么程序就会进入死锁状态。我们可以采用Wait来解决,在调用Wait时加入超时时限就可以。 if (Monitor.TryEnter(Names) Monitor.Wait(Names, 1000); / ! Names.Remove(name); Console.WriteLine(Del: 0, Names.Count); Monitor.Pulse(Names); 互斥锁(lock) lock关键字是实现线程同步的比较简单的方式,其实就是设置一个临界区。在 lock之后的.区块为一个临界区,当进入临界区时加互斥锁,离开临界区时释放互斥锁。MSDN对lock关键字的描述是: lock 关键字可将语句块标记为临界区,方法是获取给定对象的互斥锁,执行语句,然后释放该锁。 具体例子如下: static public void ThreadFunc(object name) string str = name as string; Random rand = new Random(); int count = rand.Next(100, 200); for (int i = 0; i count; i+) lock (NumList) NumList.Add(i); Console.WriteLine(0 1, str, i); 对lock的使用有几点建议:对实例锁定lock(this),对静态变量锁定lock(typeof(val)。lock的对象访问权限最好是private,否则会出现失去访问控制现象。 读写锁(ReadWriteLock) 读写锁的出现主要是在很多情况下,我们读资源的操作要多于写资源的操作。但是如果每 次只对资源赋予一个线程的访问权限显然是低效的,读写锁的优势是同时可以有多个线程对同一资源进行读操作。因此在读操作比写操作多很多,并且写操作的时间 很短的情况下使用读写锁是比较有效率的。读写锁是一个非静态类所以你在使用前需要先声明一个读写锁对象: static private ReaderWriterLock _rwlock = new ReaderWriterLock(); 读写锁是通过调用AcquireReaderLock,ReleaseReaderLock,AcquireWriterLock,ReleaseWriterLock来完成读锁和写锁控制的 static public void ReaderThread(int thrdId) try / 请求读锁,如果100ms超时退出 _rwlock.AcquireReaderLock(10); try int inx = _rand.Next(_list.Count); if (inx _list.Count) Console.WriteLine(0thread 1, thrdId, _listinx); finally _rwlock.ReleaseReaderLock(); catch (ApplicationException) / 如果请求读锁失败 Console.WriteLine(0thread get reader lock out time!, thrdId); static public void WriterThread() try / 请求写锁 _rwlock.AcquireWriterLock(100); try string val = _rand.Next(200).ToString(); _list.Add(val); / 写入资源 Console.WriteLine(writer thread has written 0, val); finally / 释放写锁 _rwlock.ReleaseWriterLock(); catch (ApplicationException) Console.WriteLine(Get writer thread lock out time!); 如果你想在读的时候插入写操作请使用UpgradeToWriterLock和DowngradeFromWriterLock来进行操作,而不是释放读锁。 static private void UpgradeAndDowngrade(int thrdId) try _rwlock.AcquireReaderLock(10); try try / 提升读锁到写锁 LockCookie lc = _rwlock.UpgradeToWriterLock(100); try string val = _rand.Next(500).ToString(); _list.Add(val); Console.WriteLine(Upgrade Thread0 add 1, thrdId, val); finally / 下降写锁 _rwlock.DowngradeFromWriterLock(ref lc); catch (ApplicationException) Console.WriteLine(0thread upgrade reader lock failed!, thrdId); finally / 释放原来的读锁 _rwlock.ReleaseReaderLock(); catch (ApplicationException) Console.WriteLine(0thread get reader lock out time!, thrdId); 这里有一点要注意的就是读锁和写锁的超时等待时间间隔的设置。通常情况下设置写锁的等待超时要比读锁的长,否则会经常发生写锁等待失败的情况。 系统内核对象 互斥对象(Mutex) 互斥对象的作用有点类似于监视器对象,确保一个代码块在同一时刻只有一个线程在执 行。互斥对象和监视器对象的主要区别就是,互斥对象一般用于跨进程间的线程同步,而监视器对象则用于进程内的线程同步。互斥对象有两种:一种是命名互斥; 另一种是匿名互斥。在跨进程中使用到的就是命名互斥,一个已命名的互斥就是一个系统级的互斥,它可以被其他进程所使用,只要在创建互斥时指定打开互斥的名 称就可以。在.Net中互斥是通过Mutex类来实现。 其实对于OpenExisting函数有两个重载版本, Mutex.OpenExisting (String) Mutex.OpenExisting (String, MutexRights) 对于默认的第一个函数其实是实现了第二个函数 MutexRights.Synchronize|MutexRights.Modify操作。 由于监视器的设计是基于.Net框架,而Mutex类是系统内核对象封装了win32的一个内核结构来实现互斥,并且互斥操作需要请求中断来完成,因此在进行进程内线程同步的时候性能上要比互斥要好。 典型的使用Mutex同步需要完成三个步骤的操作:1.打开或者创建一个Mutex实例;2.调用WaitOne()来请求互斥对象;3.最后调用ReleaseMutex来释放互斥对象。 static public void AddString(string str) / 设置超时时限并在wait前退出非默认托管上下文 if (_mtx.WaitOne(1000, true) _resource.Add(str); _mtx.ReleaseMutex(); 需要注意的是,WaitOne和ReleaseMutex必须成对出现,否则会导致进程死锁的发生,这时系统(.Net2.0)框架会抛出AbandonedMutexException异常。 信号量(Semaphore) 信号量就像一个夜总会:它有确切的容量,并被保镖控制。一旦满员,就没有人能再进入,其他人必须在外面排队。那么在里面离开一个人后,队头的人就可以进入。信号量的构造函数需要提供至少两个参数-现有的人数和最大的人数。 信号量的行为有点类似于Mutex或是lock,但是信号量没有拥有者。任意线程都可以调用Release来释放信号量而不像Mutex和lock那样需要线程得到资源才能释放。 class SemaphoreTest static Semaphore s = new Semaphore(3, 3); / 当前值=3; 容量=3 static void Main() for (int i = 0; i 10; i+) new Thread(Go).Start(); static void Go() while (true) s.WaitOne(); Thread.Sleep(100); / 一次只有个线程能被处理 s.Release(); 事件(ManualResetEvent/AutoResetEvent) AutoResetEvent 一个AutoResetEvent象是一个检票轮盘:插入一张通行证然后让一个 人通过。auto的意思就是这个轮盘自动关闭或者打开让某人通过。线程将在调用WaitOne后进行等待或者是阻塞,并且通过调用Set操作来插 入线程。如果一堆线程调用了WaitOne操作,那么轮盘就会建立一个等待队列。一个通行证可以来自任意一个线程,换句话说任意一个线程都可以通过访 问AutoResetEvent对象并调用Set来释放一个阻塞的线程。 如果在Set被调用的时候没有线程等待,那么句柄就会一直处于打开状态直到有线程调 用了WaitOne操作。这种行为避免了竞争条件-当一个线程还没来得急释放而另一个线程就开始进入的情况。因此重复的调用Set操作一个轮盘哪怕是 没有等待线程也不会一次性的让所有线程进入。 WaitOne操作接受一个超时参数-当发生等待超时的时候,这个方法会返回一个 false。当已有一个线程在等待的时候,WaitOne操作可以指定等待还是退出当前同步上下文。Reset操作提供了关闭轮盘的操作。 AutoResetEvent能够通过两个方法来创建: 1.调用构造函数 EventWaitHandle wh = new AutoResetEvent (false); 如果boolean值为true,那么句柄的Set操作将在创建后自动被调用 ;2. 通过基类EventWaitHandle方式 EventWaitHandle wh = new EventWaitHandle (false, EventResetMode.Auto); EventWaitHandle构造函数允许创建一个ManualResetEvent。人们应该通过调用Close来释放一个Wait Handle在它不再使用的时候。当在应用程序的生存期内Wait handle继续被使用,那么如果遗漏了Close这步,在应用程序关闭的时候也会被自动释放。 class BasicWaitHandle static EventWaitHandle wh = new AutoResetEvent(false); static void Main() new Thread(Waiter).Start(); Thread.Sleep(1000); / 等待一会儿 wh.Set(); / 唤醒 static void Waiter() Console.WriteLine(Waiting.); wh.WaitOne(); / 等待唤醒 Console.WriteLine(Notified); ManualResetEvent ManualResetEvent是AutoResetEvent的一个特例。它的 不同之处在于在线程调用WaitOne后不会自动的重置状态。它的工作机制有点象是开关:调用Set打开并允许其他线程进行WaitOne;调用 Reset关闭那么排队的线程就要等待,直到下一次打开。可以使用一个带volatile声明的boolean字段来模拟间断休眠 - 通过重复检测标志,然后休眠一小段时间。 ManualResetEvent常常被用于协助完成一个特殊的操作,或者让一个线程在开始工作前完成初始化。 线程池(Thread Pooling) 如果你的应用程序拥有大量的线程并花费大量的时间阻塞在一个Wait Handle上,那么你要考虑使用线程池(Thead pooling)来处理。线程池通过合并多个Wait Handle来节约等待的时间。当Wait Handle被激活时,使用线程池你需要注册一个Wait Handle到一个委托去执行。通过调用ThreadPool.RegisterWaitForSingleObject方法: class Test static ManualResetEvent starter = new ManualResetEvent(false); public static void Main() ThreadPool.RegisterWaitForSingleObject(starter, Go, hello, -1, true); Thread.Sleep(5000); Console.WriteLine(Signaling worker.); starter.Set(); Console.ReadLine(); public static void Go(object data, bool timedOut) Console.WriteLine(Started + data); / Perform task. 对于Wait Handle和委托,RegisterWaitForSingleObject接受一个黑盒
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 人教版小学英语五年级下册Unit 2 My favorite season Part C Story time课件
- 草地资源生态修复工程
- 虚拟现实交互技术设计与实现
- 建房临时用地协议书
- 布染色加工合同范本
- 废钢收购合同协议书
- 油烟净化设施协议书
- 工地车辆分包协议书
- 法院导师结对协议书
- 年会策划书协议合同
- 2025年耳鼻喉医师面试题及答案
- 2025年生态环境执法大练兵比武竞赛理论考试题库(含答案)
- 家用空调维修培训
- 2025年湖南省长沙市生地会考试卷附带长郡月亮岛中学生地会考及答案
- 2025安徽合肥水务集团有限公司招聘56人笔试考试备考试题及答案解析
- 江西省稳派智慧上进2025-2026学年高二上学期11月期中语文试卷及答案
- 污水处理厂管道安装施工方案
- DGTJ08-2048-2024 民用建筑电气防火设计标准
- 2025江西抚州市5家市属国有企业招聘51人笔试考试备考试题及答案解析
- (2025年)(完整版)中药材及中药饮片培训试题附答案
- 2025年智能城市大数据中心建设方案
评论
0/150
提交评论