当前位置: 首页 > 科技观察

面试官:你在开发的时候用过读写锁吗?

时间:2023-03-19 14:11:26 科技观察

本文转载自微信公众号《精益码农》,作者小马甲。转载本文请联系精益码农公众号。之前实现了一个具有值变化通知能力的字典类(线程不安全)。你们有没有注意到演示代码使用了锁语法糖?对于数据采集组件DAL.Connection,我们需要在切换连接配置时清空数据库连接池,这涉及到切换连接时触发变更通知。这样在高并发下就会出现问题:大多数时候,DBA不会改变业务方的数据库连接。这是一个读多写少的场景。大多数时候我们不假思索地使用锁,人为地阻止请求。这时候我们就不得不想到读写锁ReaderWriterLockSlim。好宝:ReaderWriterLockSlim使用ReaderWriterLockSlim来保护一个多线程一次读一个线程写入的资源。ReaderWriterLockSlim允许多个线程处于读模式,允许一个线程处于独占所有权的写模式锁,并允许一个具有读权限的线程处于可升级的读模式,从该线程可以升级到写模式而无需放弃对资源的读取访问权限。简而言之:ReaderWriterLockSlim提供了在某个时刻对资源的访问多线程同时读取或单线程独占写入。此外,ReaderWriterLockSlim还提供了从读模式到独占写模式的无缝升级。总结一下:读写锁有以下四种状态:1.未进入:没有线程进入锁(或者所有线程退出锁)2.读模式:每次调用EnterReadlock,锁计数递增,但您可以阅读代码块。3.Writemode:exclusive,exclusive4.Upgradeablereadmode(可升级读模式):多线程读,其中一个线程有可能在某一时刻升级为独占写模式。BTW,相对于常规锁,读写锁还有锁超时机制,可以避免因不明原因持续占有锁而导致的死锁。这非常适合我们开发读多写少的DAL.Connection组件的场景。微软的ReaderWriterLockSlim页面还贴心地提供了一个基于读写锁的缓存操作封装类SynchronizedCache。开箱即用的缓存操作类在ReaderWriterLockSlim的基础上封装了线程不安全的Dictionary,可以作为读多写少的缓存操作类。publicclassSynchronizedCache{privateReaderWriterLockSlimcacheLock=newReaderWriterLockSlim();privateDictionaryinnerCache=newDictionary();publicintCount{get{returninnerCache.Count;}}publicstringRead(intkey){cacheLock.EnterReadLock();try{returninnerCache[key];}finally{cacheLock.ExitReadLock();}}publicvoidAdd(intkey,stringvalue){cacheLock.EnterWriteLock();try{innerCache.Add(key,value);}finally{cacheLock.ExitWriteLock();}}publicboolAddWithTimeout(intkey,stringvalue,inttimeout){if(cacheLock.TryEnterWriteLock(timeout)){try{innerCache.Add(key,value);}finally{cacheLock.ExitWriteLock();}returntrue;}else{returnfalse;}}publicAddOrUpdateStatusAddOrUpdate(intkey,stringvalue){cacheLock.EnterUpgradeableReadLock();try{stringresult=null;if(innerCache.TryGetValue(key,outresult)){if(result==value){returnAddOrUpdateStatus.Unchanged;}else{cacheLock.EnterWriteLock();try{innerCache[key]=value;}finally{cacheLock.ExitWriteLock();}返回AddOrUpdateStatus.Updated;}}else{cacheLock.EnterWriteLock();try{innerCache.Add(key,value);}finally{cacheLock.ExitWriteLock();}returnAddOrUpdateStatus.Added;}}finally{cacheLock.ExitUpgradeableReadLock();}}publicvoidDelete(intkey){cacheLock.EnterWriteLock();try{innerCache.Remove(key);}finally{cacheLock.ExitWriteLock();}}publicenumAddOrUpdateStatus{Added,Updated,Unchanged};~SynchronizedCache(){if(cacheLock!=null)cacheLock.Dispose();}}缓存操作类SynchronizedCache每次操作都会返回操作结果。与普通词典一样,它不具备通知值变化的能力。我们还是像文章《面试官:实现一个带值变更通知能力的Dictionary》那样添加值变化事件,注册变化逻辑publiceventEventHandler>OnValueChanged;//---摘自AddOrUpdate方法cacheLock.EnterWriteLock();try{OnValueChanged?.Invoke(this,newValueChangedEventArgs(key));innerCache[key]=value;}finally{cacheLock.ExitWriteLock();}returnAddOrUpdateStatus.Updated;//---if(sc.AddOrUpdate(key,value)==SynchronizedCache.AddOrUpdateStatus.Updated){Console.WriteLine($"hashappenedvaluechanged时,原key对应的key值已经被改写。》);}}输出旁白本文记录了日常开发中读写锁的实践,大部分场景都是读多写少,读者可以思考是否也可以用SynchronizedCache来代替项目中的无脑锁。这篇文章就是了是同程艺龙DAL.Connection组件开发过程中的一个小插曲,有兴趣的读者可以向上滑动了解来龙去脉和小马家的思考过程。