C#多线程编程中的锁系统(四):自旋锁
目录
一:基础
二:自旋锁示例
三:SpinLock
四:继续SpinLock
五:总结
一:基础
内核锁:基于内核对象构造的锁机制,就是通常说的内核构造模式。用户模式构造和内核模式构造
优点:cpu利用最大化。它发现资源被锁住,请求就排队等候。线程切换到别处干活,直到接受到可用信号,线程再切回来继续处理请求。
缺点:托管代码->用户模式代码->内核代码损耗、线程上下文切换损耗。
在锁的时间比较短时,系统频繁忙于休眠、切换,是个很大的性能损耗。
自旋锁:原子操作+自循环。通常说的用户构造模式。 线程不休眠,一直循环尝试对资源访问,直到可用。
优点:完美解决内核锁的缺点。
缺点:长时间一直循环会导致cpu的白白浪费,高并发竞争下、CPU的消耗特别严重。
混合锁:内核锁+自旋锁。混合锁是先自旋锁一段时间或自旋多少次,再转成内核锁。
优点:内核锁和自旋锁的折中方案,利用前二者优点,避免出现极端情况(自旋时间过长,内核锁时间过短)。
缺点:自旋多少时间、自旋多少次,这些策略很难把控。
ps:操作系统或net框架,这块算法策略做的已经非常优了,有些API函数也提供了时间及次数可配置项,让开发者根据需求自行判断。
二:自旋锁示例
来看下我们自己简单实现的自旋锁:
intsignal=0; varli=newList<int>(); Parallel.For(0,1000*10000,r=> { while(Interlocked.Exchange(refsignal,1)!=0)//加自旋锁 { //黑魔法 } li.Add(r); Interlocked.Exchange(refsignal,0); //释放锁 }); Console.WriteLine(li.Count); //输出:10000000
上面就是自旋锁:Interlocked.Exchange+while
1:定义signal 0可用,1不可用。
2:Parallel模拟并发竞争,原子更改signal状态。后续线程自旋访问signal,是否可用。
3:A线程使用完后,更改signal为0。剩余线程竞争访问资源,B线程胜利后,更改signal为1,失败线程继续自旋,直到可用。
三:SpinLock
SpinLock是net4.0后系统帮我们实现的自旋锁,内部做了优化。
简单看下实例:
varli=newList<int>(); varsl=newSpinLock(); Parallel.For(0,1000*10000,r=> { boolgotLock=false; //释放成功 sl.Enter(refgotLock); //进入锁 li.Add(r); if(gotLock)sl.Exit(); //释放 }); Console.WriteLine(li.Count); //输出:10000000
四:继续SpinLock
newSpinLock(false) 这个构造函数主要用来帮我们检查死锁用,true是开启。
开启状态下,如果发生死锁会直接抛异常的。
贴了一部分源码(已折叠),我们来看下:
publicvoidEnter(refboollockTaken) { if(lockTaken) { lockTaken=false; thrownewSystem.ArgumentException(Environment.GetResourceString("SpinLock_TryReliableEnter_ArgumentException")); }
//Fastpathtoacquirethelockifthelockisreleased //Ifthethreadtrackingenabledsetthenewownertothecurrentthreadid //Idnot,settheanonymousbitlock intobservedOwner=m_owner; intnewOwner=0; boolthreadTrackingEnabled=(m_owner&LOCK_ID_DISABLE_MASK)==0; if(threadTrackingEnabled) { if(observedOwner==LOCK_UNOWNED) newOwner=Thread.CurrentThread.ManagedThreadId; } elseif((observedOwner&LOCK_ANONYMOUS_OWNED)==LOCK_UNOWNED) { newOwner=observedOwner|LOCK_ANONYMOUS_OWNED;//setthelockbit } if(newOwner!=0) { #if!FEATURE_CORECLR Thread.BeginCriticalRegion(); #endif
#ifPFX_LEGACY_3_5 if(Interlocked.CompareExchange(refm_owner,newOwner,observedOwner)==observedOwner) { lockTaken=true; return; } #else if(Interlocked.CompareExchange(refm_owner,newOwner,observedOwner,reflockTaken)==observedOwner) { //Fastpathsucceeded return; } #endif #if!FEATURE_CORECLR Thread.EndCriticalRegion(); #endif } //Fastpathfailed,tryslowpath ContinueTryEnter(Timeout.Infinite,reflockTaken); } privatevoidContinueTryEnter(intmillisecondsTimeout,refboollockTaken) { longstartTicks=0; if(millisecondsTimeout!=Timeout.Infinite&&millisecondsTimeout!=0) { startTicks=DateTime.UtcNow.Ticks; }
#if!FEATURE_PAL&&!FEATURE_CORECLR //PALdoesn'tsupport eventing,andwedon'tcompileCDSprovidersforCoreclr if(CdsSyncEtwBCLProvider.Log.IsEnabled()) { CdsSyncEtwBCLProvider.Log.SpinLock_FastPathFailed(m_owner); } #endif
if(IsThreadOwnerTrackingEnabled) { //Slowpathforenabledthreadtrackingmode ContinueTryEnterWithThreadTracking(millisecondsTimeout,startTicks,reflockTaken); return; }
//thenthreadtrackingisdisabled //Inthiscasetherearethreewaystoacquirethelock //1-thefirstwaythethreadeithertriestogetthelockifit'sfreeorupdatesthewaiters,iftheturn>=theprocessorscountthengoto3elsegoto2 //2-Inthisstepthewaiterthreadsspinsandtriestoacquirethelock,thenumberofspiniterationsandspincountisdependentonthethreadturn //thelatethethreadarrivesthemoreitspinsandlessfrequentitcheckthelockavilability //Alsothespinscountisincreaeseachiteration //Ifthespinsiterationsfinishedandfailedtoacquirethelock,gotostep3 //3-Thisistheyieldingstep,therearetwowaysofyieldingThread.YieldandSleep(1) //Ifthetimeoutisexpiredinafterstep1,weneedtodecrementthewaiterscountbeforereturning intobservedOwner;
//***Step1,takethelockorupdatethewaiters //trytoacquirethelockdirectlyifpossobleorupdatethewaiterscount SpinWaitspinner=newSpinWait(); while(true) { observedOwner=m_owner; if((observedOwner&LOCK_ANONYMOUS_OWNED)==LOCK_UNOWNED) { #if!FEATURE_CORECLR Thread.BeginCriticalRegion(); #endif #ifPFX_LEGACY_3_5 if(Interlocked.CompareExchange(refm_owner,observedOwner|1,observedOwner)==observedOwner) { lockTaken=true; return; } #else if(Interlocked.CompareExchange(refm_owner,observedOwner|1,observedOwner,reflockTaken)==observedOwner) { return; } #endif
#if!FEATURE_CORECLR Thread.EndCriticalRegion(); #endif } else//failedtoacquirethelock,thentrytoupdatethewaiters.Ifthewaiterscountreachedthemaximum,jsutbreakthelooptoavoidoverflow if((observedOwner&WAITERS_MASK)== MAXIMUM_WAITERS||Interlocked.CompareExchange(refm_owner,observedOwner+2,observedOwner)==observedOwner) break; spinner.SpinOnce(); }
//Checkthetimeout. if(millisecondsTimeout==0|| (millisecondsTimeout!=Timeout.Infinite&& TimeoutExpired(startTicks,millisecondsTimeout))) { DecrementWaiters(); return; }
//***Step2.Spinning //lockacquiredfailedandwaitersupdated intturn=((observedOwner+2)&WAITERS_MASK)/2; intprocessorCount=PlatformHelper.ProcessorCount; if(turn<processorCount) { intprocessFactor=1; for(inti=1;i<=turn*SPINNING_FACTOR;i++) { Thread.SpinWait((turn+i)*SPINNING_FACTOR*processFactor); if(processFactor<processorCount) processFactor++; observedOwner=m_owner; if((observedOwner&LOCK_ANONYMOUS_OWNED)==LOCK_UNOWNED) { #if!FEATURE_CORECLR Thread.BeginCriticalRegion(); #endif intnewOwner=(observedOwner&WAITERS_MASK)==0?//Getsthenumberofwaiters,ifzero observedOwner|1//don'tdecrementit.justsetthelockbit,itiszzerobecauseapreviouscallofExit(false)ehichcorruptedthewaiters :(observedOwner-2)|1;//otherwisedecrementthewaitersandsetthelockbit Contract.Assert((newOwner&WAITERS_MASK)>=0); #ifPFX_LEGACY_3_5 if(Interlocked.CompareExchange(refm_owner,newOwner,observedOwner)==observedOwner) { lockTaken=true; return; } #else if(Interlocked.CompareExchange(refm_owner,newOwner,observedOwner,reflockTaken)==observedOwner) { return; } #endif
#if!FEATURE_CORECLR Thread.EndCriticalRegion(); #endif } } }
//Checkthetimeout. if(millisecondsTimeout!=Timeout.Infinite&&TimeoutExpired(startTicks,millisecondsTimeout)) { DecrementWaiters(); return; }
//***Step3,Yielding //Sleep(1)every50yields intyieldsoFar=0; while(true) { observedOwner=m_owner; if((observedOwner&LOCK_ANONYMOUS_OWNED)==LOCK_UNOWNED) { #if!FEATURE_CORECLR Thread.BeginCriticalRegion(); #endif intnewOwner=(observedOwner&WAITERS_MASK)==0?//Getsthenumberofwaiters,ifzero observedOwner|1//don'tdecrementit.justsetthelockbit,itiszzerobecauseapreviouscallofExit(false)ehichcorruptedthewaiters :(observedOwner-2)|1;//otherwisedecrementthewaitersandsetthelockbit Contract.Assert((newOwner&WAITERS_MASK)>=0); #ifPFX_LEGACY_3_5 if(Interlocked.CompareExchange(refm_owner,newOwner,observedOwner)==observedOwner) { lockTaken=true; return; } #else if(Interlocked.CompareExchange(refm_owner,newOwner,observedOwner,reflockTaken)==observedOwner) { return; } #endif #if!FEATURE_CORECLR Thread.EndCriticalRegion(); #endif }
if(yieldsoFar%SLEEP_ONE_FREQUENCY==0) { Thread.Sleep(1); } elseif(yieldsoFar%SLEEP_ZERO_FREQUENCY==0) { Thread.Sleep(0); } else { #ifPFX_LEGACY_3_5 Platform.Yield(); #else Thread.Yield(); #endif } if(yieldsoFar%TIMEOUT_CHECK_FREQUENCY==0) { //Checkthetimeout. if(millisecondsTimeout!=Timeout.Infinite&&TimeoutExpired(startTicks,millisecondsTimeout)) { DecrementWaiters(); return; } }
yieldsoFar++; } } ///<summary> ///decrementsthewaiters,incaseofthetimeoutisexpired ///</summary> privatevoidDecrementWaiters() { SpinWaitspinner=newSpinWait(); while(true) { intobservedOwner=m_owner; if((observedOwner&WAITERS_MASK)==0)return;//don'tdecrementthewaitersifit'scorruptedbypreviouscallofExit(false) if(Interlocked.CompareExchange(refm_owner,observedOwner-2,observedOwner)==observedOwner) { Contract.Assert(!IsThreadOwnerTrackingEnabled);//Makesurethewaitersneverbenegativewhichwillcausethethreadtrackingbittobeflipped break; } spinner.SpinOnce(); } }