今天小编给大家分享一下C#读写锁实例分析的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。

ReaderWriterLockSlim

ReaderWriterLock 类:定义支持单个写线程和多个读线程的锁。

ReaderWriterLockSlim 类:表示用于管理资源访问的锁定状态,可实现多线程读取或进行独占式写入访问。

两者的 API 十分接近,而且 ReaderWriterLockSlim 相对 ReaderWriterLock 来说 更加安全。因此本文主要讲解 ReaderWriterLockSlim 。

两者都是实现多个线程可同时读取、只允许一个线程写入的类。

ReaderWriterLockSlim

老规矩,先大概了解一下 ReaderWriterLockSlim 常用的方法。

常用方法方法说明EnterReadLock()尝试进入读取模式锁定状态。EnterUpgradeableReadLock()尝试进入可升级模式锁定状态。EnterWriteLock()尝试进入写入模式锁定状态。ExitReadLock()减少读取模式的递归计数,并在生成的计数为 0(零)时退出读取模式。ExitUpgradeableReadLock()减少可升级模式的递归计数,并在生成的计数为 0(零)时退出可升级模式。ExitWriteLock()减少写入模式的递归计数,并在生成的计数为 0(零)时退出写入模式。TryEnterReadLock(Int32)尝试进入读取模式锁定状态,可以选择整数超时时间。TryEnterReadLock(TimeSpan)尝试进入读取模式锁定状态,可以选择超时时间。TryEnterUpgradeableReadLock(Int32)尝试进入可升级模式锁定状态,可以选择超时时间。TryEnterUpgradeableReadLock(TimeSpan)尝试进入可升级模式锁定状态,可以选择超时时间。TryEnterWriteLock(Int32)尝试进入写入模式锁定状态,可以选择超时时间。TryEnterWriteLock(TimeSpan)尝试进入写入模式锁定状态,可以选择超时时间。

ReaderWriterLockSlim 的读、写入锁模板如下:

privatestaticReaderWriterLockSlimtoolLock=newReaderWriterLockSlim();//读privateTRead(){try{toolLock.EnterReadLock();//获取读取锁returnobj;}catch{}finally{toolLock.ExitReadLock();//释放读取锁}returndefault;}//写publicvoidWrite(intkey,intvalue){try{toolLock.EnterUpgradeableReadLock();try{toolLock.EnterWriteLock();/***/}catch{}finally{toolLock.ExitWriteLock();}}catch{}finally{toolLock.ExitUpgradeableReadLock();}}订单系统示例

这里来模拟一个简单粗糙的订单系统。

开始编写代码前,先来了解一些方法的具体使用。

EnterReadLock()/TryEnterReadLockExitReadLock()成对出现。

EnterWriteLock()/TryEnterWriteLock()ExitWriteLock()成对出现。

EnterUpgradeableReadLock()进入可升级的读模式锁定状态。

EnterReadLock()使用EnterUpgradeableReadLock()进入升级状态,在恰当时间点 通过EnterWriteLock()进入写模式。(也可以倒过来)

定义三个变量:

ReaderWriterLockSlim 多线程读写锁;

MaxId 当前订单 Id 的最大值;

orders 订单表;

privatestaticReaderWriterLockSlimtool=newReaderWriterLockSlim();//读写锁privatestaticintMaxId=1;publicstaticList<DoWorkModel>orders=newList<DoWorkModel>();//订单表

//订单模型publicclassDoWorkModel{publicintId{get;set;}//订单号publicstringUserName{get;set;}//客户名称publicDateTimeDateTime{get;set;}//创建时间}

然后实现查询和创建订单的两个方法。

分页查询订单:

在读取前使用EnterReadLock()获取锁;

读取完毕后,使用ExitReadLock()释放锁。

这样能够在多线程环境下保证每次读取都是最新的值。

//分页查询订单privatestaticDoWorkModel[]DoSelect(intpageNo,intpageSize){try{DoWorkModel[]doWorks;tool.EnterReadLock();//获取读取锁doWorks=orders.Skip((pageNo-1)*pageSize).Take(pageSize).ToArray();returndoWorks;}catch{}finally{tool.ExitReadLock();//释放读取锁}returndefault;}

创建订单:

创建订单的信息十分简单,知道用户名和创建时间就行。

订单系统要保证的时每个 Id 都是唯一的(实际情况应该用Guid),这里为了演示读写锁,设置为 数字。

在多线程环境下,我们不使用Interlocked.Increment(),而是直接使用+= 1,因为有读写锁的存在,所以操作也是原则性的。

//创建订单privatestaticDoWorkModelDoCreate(stringuserName,DateTimetime){try{tool.EnterUpgradeableReadLock();//升级try{tool.EnterWriteLock();//获取写入锁//写入订单MaxId+=1;//Interlocked.Increment(refMaxId);DoWorkModelmodel=newDoWorkModel{Id=MaxId,UserName=userName,DateTime=time};orders.Add(model);returnmodel;}catch{}finally{tool.ExitWriteLock();//释放写入锁}}catch{}finally{tool.ExitUpgradeableReadLock();//降级}returndefault;}

Main 方法中:

开 5 个线程,不断地读,开 2 个线程不断地创建订单。线程创建订单时是没有设置Thread.Sleep()的,因此运行速度十分快。

Main 方法里面的代码没有什么意义。

staticvoidMain(string[]args){//5个线程读for(inti=0;i<5;i++){newThread(()=>{while(true){varresult=DoSelect(1,MaxId);if(resultisnull){Console.WriteLine("获取失败");continue;}foreach(variteminresult){Console.Write($"{item.Id}|");}Console.WriteLine("\n");Thread.Sleep(1000);}}).Start();}for(inti=0;i<2;i++){newThread(()=>{while(true){varresult=DoCreate((newRandom().Next(0,100)).ToString(),DateTime.Now);//模拟生成订单if(resultisnull)Console.WriteLine("创建失败");elseConsole.WriteLine("创建成功");}}).Start();}}

在 ASP.NET Core 中,则可以利用读写锁,解决多用户同时发送 HTTP 请求带来的数据库读写问题。

这里就不做示例了。

如果另一个线程发生问题,导致迟迟不能交出写入锁,那么可能会导致其它线程无限等待。

那么可以使用TryEnterWriteLock()并且设置等待时间,避免阻塞时间过长。

boolisGet=tool.TryEnterWriteLock(500);并发字典写示例

因为理论的东西,笔者这里不会说太多,主要就是先掌握一些 API(方法、属性) 的使用,然后简单写出示例,后面再慢慢深入了解底层原理。

这里来写一个多线程共享使用字典(Dictionary)的使用示例。

增加两个静态变量:

privatestaticReaderWriterLockSlimtoolLock=newReaderWriterLockSlim();privatestaticDictionary<int,int>dict=newDictionary<int,int>();

实现一个写操作:

publicstaticvoidWrite(intkey,intvalue){try{//升级状态toolLock.EnterUpgradeableReadLock();//读,检查是否存在if(dict.ContainsKey(key))return;try{//进入写状态toolLock.EnterWriteLock();dict.Add(key,value);}finally{toolLock.ExitWriteLock();}}finally{toolLock.ExitUpgradeableReadLock();}}

上面没有catch { }是为了更好观察代码,因为使用了读写锁,理论上不应该出现问题的。

模拟五个线程同时写入字典,由于不是原子操作,所以 sum 的值有些时候会出现重复值。

privatestaticintsum=0;publicstaticvoidAddOne(){for(inti=0;i<100_0000;i++){sum+=1;Write(sum,sum);}}staticvoidMain(string[]args){for(inti=0;i<5;i++)newThread(()=>{AddOne();}).Start();Console.ReadKey();}ReaderWriterLock

大多数情况下都是推荐 ReaderWriterLockSlim 的,而且两者的使用方法十分接近。

例如 AcquireReaderLock 是获取读锁,AcquireWriterLock 获取写锁。使用对应的方法即可替换 ReaderWriterLockSlim 中的示例。

这里就不对 ReaderWriterLock 进行赘述了。

ReaderWriterLock 的常用方法如下:

方法说明AcquireReaderLock(Int32)使用一个 Int32 超时值获取读线程锁。AcquireReaderLock(TimeSpan)使用一个 TimeSpan 超时值获取读线程锁。AcquireWriterLock(Int32)使用一个 Int32 超时值获取写线程锁。AcquireWriterLock(TimeSpan)使用一个 TimeSpan 超时值获取写线程锁。AnyWritersSince(Int32)指示获取序列号之后是否已将写线程锁授予某个线程。DowngradeFromWriterLock(LockCookie)将线程的锁状态还原为调用 UpgradeToWriterLock(Int32) 前的状态。ReleaseLock()释放锁,不管线程获取锁的次数如何。ReleaseReaderLock()减少锁计数。ReleaseWriterLock()减少写线程锁上的锁计数。RestoreLock(LockCookie)将线程的锁状态还原为调用 ReleaseLock() 前的状态。UpgradeToWriterLock(Int32)使用一个 Int32 超时值将读线程锁升级为写线程锁。UpgradeToWriterLock(TimeSpan)使用一个TimeSpan超时值将读线程锁升级为写线程锁。

以上就是“C#读写锁实例分析”这篇文章的所有内容,感谢各位的阅读!相信大家阅读完这篇文章都有很大的收获,小编每天都会为大家更新不同的知识,如果还想学习更多的知识,请关注亿速云行业资讯频道。