版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、.NET kz 进展读取操作,那么根本不需要使用锁;反之,假设线程只对资源进展写入操作,则应当使用互斥锁(比方使用 Monitor 类等)。还有一种状况,就是存在多个线程对资源进展读取操ReaderWriterLock 类.NETFrameworkBCL 在 1.1 版本时,给我们供给了一个 ReaderWriterLock 类来面对此种情景。但是很圆满,Microsoft 官方不推举使用该类。JeffreyRichter 也在他的CLRviaC#一书中对它进展了严峻的批判。下面是该类不受欢送的主要缘由:AcquireReaderLock 方法比 Monitor 类的 Enter方法要慢 5
2、倍左右,而等待争夺写锁甚至比 Monitor 类慢 6 倍。策略。假设某个线程完成写入操作后,同时面临读线程和写线程等待处理。ReaderWriterLock存在大量的读线程和格外少的写线程,这样写线程很可能必需长时间地等待,造成写线程饥饿,不能准时更数据。更槽糕的状况是,假设写线程始终等待,就会造成活锁。反之,我们让 ReaderWriterLock 实行写线程优先的策略。假设存在多个写线程,而读线程数量稀有,也会造成读线程饥饿。幸运的是,现实实践中,这种状况很少消灭。一旦发生这种状况,我们可以实行互斥锁的方法。ReaderWriterLock 类支持锁递归。这就意味着该锁清楚的知道目前哪个
3、线程拥有它。假设拥有该锁的线程递归尝试获得该读写锁,递归算法允许该线程获得该读写锁, 尽管这看起来是个很好的特性,但是实现这个“特性”代价太高。首先,由于多个读线 程可以同时拥有该读写锁,这必需让该锁为每个线程保持计数。此外,还需要额外的内 存空间和时间来更计数。这个特性对 ReaderWriterLock 类可怜的性能奉献极大。其.NET 的异步编程架构)。由于这个递归特性,ReaderWriterLock 不支持这种编程架构。资源泄漏。在 .NET 2.0 之前的版本中, ReaderWriterLock 类会造成内核对象泄露。这些对象只有在进程终止后才能再次回收。幸运的是,.NET 2.
4、0 修正了这个 Bug 。ReaderWriterLockUpgradeToWriteLock 方法。这个方法实际上在更到写锁前先释放了读锁。这就让其他线程有时机在此期间乘虚而入,从而获得读写锁且转变状态。假设先更到写锁,然后释放读锁。假设两个线程同时更将会导致另外一个线程死锁。MicrosoftReaderWriterLockSlim 类。原来可以在原有的 ReaderWriterLock 类上修正错误,但是考虑到兼容性和已存在的 API , Microsoft 放弃了这种做法。 固然也可以标记ReaderWriterLock 类为 Obsolete,但是由于某些缘由,这个类还有存在的必要。
5、ReaderWriterLockSlim 类的 ReaderWriterLockSlim 类支持三种锁定模式:Read,Write,UpgradeableRead。这三种模式对应的方法分别是 EnterReadLockEnterWriteLockEnterUpgradeableReadLock 。 再就是与此对应的 kkk ExitReadLock,ExitWriteLock,ExitUpgradeableReadLock。Read 和 Writer 锁定模式比较简洁易懂:Read 模式是典型的共享锁定模式,任意数量的线程都可以在该模式下同时获 得锁;Writer 模式则是互斥模式,在该模式下
6、只允许一个线程进入该锁。UpgradeableRead锁定模式可能对于大多数人来说比较颖,但是在数据库领域却众所周知。这个的读写锁类性能跟 Monitor 类大致相当,或许在 Monitor 类的 2 倍之内。而且锁优先让写线程获得锁,由于写操作的频率远小于读操作。通常这会导致更好的可伸缩性。起初,ReaderWriterLockSlimCTP 的代码还PrefersReaders, PrefersWritersAndUpgrades 和 Fifo 等竞争策略。但是这些策略虽然添加起来格外简洁,但是会导致状况格外的简单。所以 Microsoft 最终打算供给一个能够在大多数状况下良好工作的简洁
7、模型。ReaderWriterLockSlim 的更锁现在让我们更加深入的争辩一下更模型。UpgradeableRead 锁定模式允许安全的从 Read或 Write 模式下更。还记得从前 ReaderWriterLock 的更是非原子性,危急的操作吗致死锁。锁一次只允许一个线程处在 UpgradeableRead 模式下。using System;using System.Linq;using System.Threading;namespace Lucifer.CSharp.Sampleclass Programprivate ReaderWriterLockSlim rwLock = n
8、ew ReaderWriterLockSlim;void Samplebool isUpdated = true; rwLock.EnterUpgradeableReadLock;using System;using System.Linq;using System.Threading;namespace Lucifer.CSharp.Sampleclass Programprivate ReaderWriterLockSlim rwLock = new ReaderWriterLockSlim;void Samplebool isUpdated = true; rwLock.EnterUpg
9、radeableReadLock;tryif(/*/)rwLock.EnterWriteLock;try/finallyfinallyrwLock.ExitWriteLock;elserwLock.EnterReadLock; rwLock.ExitUpgradeableReadLock;isUpdated = false;try/finallyrwLock.ExitReadLock;finallyif (isUpdated) rwLock.ExitUpgradeableReadLock;ReaderWriterLockSlim 的递归策略和升级到写锁之外, 全部的递归恳求都不允许。这意味着你
10、不能连续两次调用EnterReadLockCLR 将会抛出 LockRecursionException特别。固然,你可以使用 LockRecursionPolicy.SupportsRecursion 的构造函数参数让该读写锁支持递归锁定。但不建议对的开发使用递归,由于递归会带来不必要的简单状况, 从而使你的代码更简洁消灭死锁现象。时恳求写锁。Microsoft 曾经考虑供给这样的支持,但是这种状况太简洁导致死锁。所以Microsoft 最终放弃了这个方案。如 IsReadLockHeld, IsWriteLockHeld 和 IsUpgradeableReadLockHeld 。你也可以通
11、过WaitingReadCount,WaitingWriteCount 和 WaitingUpgradeCount 等属性来查看有多少线程正在等待持有特定模式下的锁。CurrentReadCount 属性则告知目前有多少并发读线程。RecursiveReadCountRecursiveWriteCount 和 RecursiveUpgradeCount 则告知目前线程进入特定模式锁定状态下的次数。小结这篇文章分析了 .NET 中供给的两个读写锁类。然而 .NET 3.5 供给的读写锁ReaderWriterLockSlim 类 消 除 了 ReaderWriterLockReaderWrite
12、rLock 相比,性能有了极大提高。更具有原子性,也可以极大避开死锁。更ReaderWriterLockSlim 来代替ReaderWriterLock 类。Update 于 2022-12-07 0:06WindowsVista 及其以后的版本增了一个 SRWLockWindows 内核大事机制为根底而构建。它的设计比较有意思。SRW 锁不支持递归。Windows Kernel 团队认为支持递归会造成额外系统开销,缘由是为了维SRW 锁也不支持从共享访问升级到独占访问,同时也不支持从触。我对其进展了 .NET 封装。代码如下:usingusing System;using System.Th
13、reading;using System.Runtime.InteropServices;namespace Lucifer.Threading.Lock/ / Windows NT 6.0 才支持的读写锁。/ / 请留意,这个类只能在 NT 6.0 及以后的版本中才能使用。public sealed class SRWLockprivate IntPtr rwLock;/ / 该锁不支持递归。/ public SRWLockInitializeSRWLock(out rwLock);/ / 获得读锁。/ public void EnterReadLockAcquireSRWLockShare
14、d(ref rwLock);/ / 获得写锁。/ public void EnterWriteLockAcquireSRWLockExclusive(ref rwLock);/ / 释放读锁。/ public void ExitReadLockReleaseSRWLockShared(ref rwLock);/ / 释放写锁。/ public void ExitWriteLockReleaseSRWLockExclusive(ref rwLock);DllImport(“Kernel32“, CallingConvention = CallingConvention.Winapi, Exact
15、Spelling = true)private static extern void InitializeSRWLock(out IntPtr rwLock);DllImport(“Kernel32“, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)private static extern void AcquireSRWLockExclusive(ref IntPtr rwLock);DllImport(“Kernel32“, CallingConvention = CallingConvention.W
16、inapi, ExactSpelling = true)private static extern void AcquireSRWLockShared(ref IntPtr rwLock);DllImport(“Kernel32“, CallingConvention = CallingConvention.Winapi, ExactSpelling = true)private static extern void ReleaseSRWLockExclusive(ref IntPtr rwLock); DllImport(“Kernel32“, CallingConvention = Cal
17、lingConvention.Winapi, ExactSpellingactSpelling = true)private static extern void ReleaseSRWLockShared(ref IntPtr rwLock);此外,在其他平台也有一些有意思的读写锁。比方 Linux 内核中的读写锁和 Java 中的读写锁。感兴趣的同学可以自己争辩一番。using System;using System;using System.Threading;namespace Lucifer.CSharp.Samplepublic class SynchronizedCachepriv
18、ate ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim; private Dictionary innerCache = new Dictionary;public string Read(int key)cacheLock.EnterReadLock;tryreturn innerCachekey;finallycacheLock.ExitReadLock;public void Add(int key, string value)cacheLock.EnterWriteLock;tryinnerCache.Add(key,
19、 value);finallycacheLock.ExitWriteLock;public bool AddWithTimeout(int key, string value, int timeout)if (cacheLock.TryEnterWriteLock(timeout)tryinnerCache.Add(key, value);finallycacheLock.ExitWriteLock;elsereturn true;return false;public AddOrUpdateStatus AddOrUpdate(int key, string value)cacheLock.
20、EnterUpgradeableReadLock;trystring result = null;if (innerCache.TryGetValue(key, out result)if (result = value)return AddOrUpdateStatus.Unchanged;elsecacheLock.EnterWriteLock;tryinnerCachekey = value;finallycacheLock.ExitWriteLock;return AddOrUpdateStatus.Updated;elsecacheLock.EnterWriteLock;tryinne
21、rCache.Add(key, value);finallycacheLock.ExitWriteLock;return AddOrUpdateStatus.Added;finallycacheLock.ExitUpgradeableReadLock;public void Delete(int key)cacheLock.EnterWriteLock;tryinnerCache.Remove(key);finallycacheLock.ExitWriteLock;public enum AddOrUpdateStatusAdded, Updated, Unchanged;再次 Update
22、于 2022-12-07 0:47lock-free 方案。但是 lock-free 有着固有缺陷:极难编码,极难证明其正确性。读写锁方案的应用范围更加广泛一些。再说 lock-free 编程lock-free 编程实在让人又爱又恨。博主以前曾经写过几篇关于 lock-free 编程的文章。比方关于无锁编程、并发数据构造:迷人的原子。假设想更加深入的了解和实践 lock-free 编程,可以参考 CLR 2.0 Memory Model、并发数据构造:Stack。这篇文章并不打算连续阐述如何使用 lock-freelock-free 有个更加全面的生疏。说到 lock-freeCASCAS 是
23、英文 CompareandSwap 的简写。在 Windows 和 .NETInterlockedAPIx86 架构 CPU 对应的汇编指令有 XCHG、CMPXCHG、INC 等,固然还得加上 LOCK 作为前缀(更多信息请看 并发数据构造:迷人的原子)。CAS 原语在轻度和中度争用状况下确实可以大幅度提高程序性能。但凡事有利必有弊,CAS原语极度扼杀了程序的可伸缩性(其他缺点请看关于无锁编程有点偏激,但事实如此。请容博主细细道来:CASIntel 和 AMD 的 CPU 承受了一种叫做MOSEICAS 操作相对本钱低廉。但一旦资源争用,就会引起缓存失效和总线占用。缓存越失效,总线越被占CA
24、SCAS 内存操作来说也是如此,但 CAS 状况更加槽糕。CAS 操作要比一般内存操作花费更多 CPU写缓冲区与穿越内存栅栏限制和需求以及编译器对 CAS 操作优化的力量。CASCAS 操作失败将导致重尝试某些指令(典型的回滚操作)。即便没有任何争用,它也会做一些无用功。不管成功或失败都会增加争用的风险。大多数 CAS 操作发生在锁进入和退出时。尽管锁可由单一 CAS 操作构建,但 .NET CLRMonitor 类却使用了两个(一个在 Enter 方法,另一个在 Exit 方法)。lock-free 算法也经常使用 CAS 原语来代替使用锁机制。但是由于内存重组,这样的算法也经常需要显式的栅
25、栏,即便使用了 CAS 指令。锁机制格外邪恶,但大多数合格的开发人员都知道让锁持有尽量少的时间。因此,虽然锁机制让人格外厌烦,且影响性能。但相对于大量,频繁的 CAS操作而言,它却并不影响程序的可伸缩性。举个很简洁的例子,增加计数 100,000,000 次。要做到这样,有几种方式。假设仅运行在单核单处理器上,我们可以使用一般的内存操作:static volatile int counter = 0;static void BaselineCounterfor (int i = 0; i Count; i+)counter+;很明显,上述代码例如不是线程安全的,但给计数器供给了一个很好的时间基
26、准。下面我们使用 LOCK INC 来作为线程安全的第一种方式:static volatile int counter = 0;static void LockIncCounterfor (int i = 0; i Count; i+)Interlocked.Increment(ref counter);些验证(比方内存溢出保护),我们通常会使用这种方式。就是使用 CMPXCHG(即 CAS):static volatile int counter = 0;static void CASCounterfor (int i = 0; i Count; i+)int oldValue;dooldV
27、alue = counter;while(Interlocked pareExchange(refcounter,oldValue+1,oldValue)!=oldValue);现在问一个有意思的问题:当缓存争用时,哪一个方法更慢?结果可能会让你大吃一惊哦。在 Intel 4 核处理器下测试结果如下:图中,当 CPU 使用 2 个核时,BaselineCounter 方法是单核单路状况的 2.11 倍。其他状况类似。通过结果比对,我们可以得知:更多的并发性导致结果更加槽糕。这很大局部缘由由内存争用所致。当 CASCASCounter 方法的在多核处理器上的性能(具体技巧可以参考夏天是个好季节兄
28、的自己动手实现一个轻量级的信号量一二)。这可以大大削减活锁和关联内联阻碍锁消耗的时间。 延迟访问共享内存可以极大缓解压力。比方插入 2 个函数调用,我们得到了如下数据:插入 64 个函数调用之后,数据又变成了如下所示:这个时候,我们看到多核所花费的时间少于单核了。这就是我们使用并行所带来的加速。看到这里,我们可能会想,既然从 2 到 64 个函数调用使得结果越来越好,那么超过 64 个函数调用岂不是会变得更好?实际上,在插入 128 个函数调用之后,加速已经到达极限。结果如下所示:如何计算加速比,请参考 并行思维 II。天下没有免费的午餐,CAS 也不例外。我们应当慎之又慎的将 lock-free CAS 代码放到我结:共享是魔鬼。它从根本上限制应用程
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025年度汽车租赁及道路救援服务合同4篇
- 疫情期间便携施工方案
- 建筑工程采购分包合同(2篇)
- 2025年度个人门面出租合同及装修设计服务4篇
- 2025年度个人医疗贷款债权转让与健康管理服务合同3篇
- 2025年度个人住宅门窗安全性能提升合同4篇
- 钢塔施工方案
- 预制桩课程设计讲解
- 钢结构课程设计加图纸
- 银杏主题课程设计
- 我的家乡琼海
- (2025)专业技术人员继续教育公需课题库(附含答案)
- 《互联网现状和发展》课件
- 【MOOC】计算机组成原理-电子科技大学 中国大学慕课MOOC答案
- 2024年上海健康医学院单招职业适应性测试题库及答案解析
- 2024年湖北省武汉市中考语文适应性试卷
- 品质部组织架构图构
- 汽车租赁行业利润分析
- 春节拜年的由来习俗来历故事
- 通信电子线路(哈尔滨工程大学)智慧树知到课后章节答案2023年下哈尔滨工程大学
- 皮肤恶性黑色素瘤-疾病研究白皮书
评论
0/150
提交评论