时间:2020-7-31来源:本站原创作者:佚名
临床白癜风研究专家 http://www.bdfyy999.com/Photo/mingyifengcai/715.html

作者:韩子卢

出处:   publicclassSimpleHybridLock:IDisposable{privateInt32m_waiters=0;privateAutoResetEventm_waiterlock=newAutoResetEvent(false);//注意这里是falsepublicvoidEnter(){if(Interlocked.Increment(refm_waiters)==1){return;}m_waiterlock.WaitOne();}publicvoidLeave(){if(Interlocked.Decrement(refm_waiters)==0){return;}m_waiterlock.Set();}publicvoidDispose(){m_waiterlock.Dispose();}}

上面的例子学了上一张后看起来感觉很简单就不讲解了,只是一个简单的,将Interlocked这种互锁构造和自动重置事件构造AutoResetEvent结合起来的,混合线程同步构造的例子。

上面混合锁可以去加入自旋,当超过一定的自旋次数时再进行阻塞。也可以去加入互斥体的递归玩法,总之这个东西充满了无限的可能。

.NET框架类库中的混合构造

总体而言,实际上就是对上面那个简单例子的扩展,它们的目的都是为了使线程能尽可能不去进入内核模式,并且减少线程竞争时自旋的性能影响。

ManualResetEventSlim类和SemaphoreSlim类

翻译过来就是手工重置事件简化构造和信号量简化构造

发生第一次竞争时才进行内核模式构造,否则为用户模式构造

可传递超时值和CancellationToken,也就是取消啦,信号量那个还能进行异步等待。

Monitor类和同步块

Monitor类是最常用的,支持递归,线程所有权和互斥

然而这个类存在一些问题,容易引发BUG。因为它是一个静态类,它的正确玩法在一定程度上和其它同步构造有所区别。

堆中的每个对象都可以关联一个叫同步块的数据结构,它为内核对象,且拥有线程ID,递归计数,等待线程计数。而Monitor类的操作就涉及到这些同步块的字段。

每个对象都有一个同步块索引,而同步块实际上是在CLR初始化的时候就创建的一个同步块数组中。

一个对象在构造时它的同步块索引为-1,就是没有关联任何同步块。而调用Monitor.Enter后CLR在同步块数组中找到个空白同步块,并设置对象的同步块索引,让它引用该同步块。Exit当然就是取消关联。

Monitor.Enter会传一个对象进去,这个对象必须为所在函数的类的私有对象,而不能传所在对象本身,这回让这个锁变成公共的。这样就会引发很多问题。所以最好的方法就是传递一个私有的只读对象。

永远不要讲String,值类型和类型对象传给Monitor.Enter。

而C#有一个lock关键字提供的简化语法就是基于Monitor的。而且其相当于在一个tryfinally结构上使用。首先不利于性能,其次还可能造成线程访问损坏的状态。所以作者建议杜绝使用lock语法。

LockToken变量默认false,只有在Enter调用后才为true,要是在Enter调用前Exit,可以考虑判断LockToken,从而避免错误的Exit。

ReaderWriterLockSlim类

一个线程向数据写入时,请求访问的其他所有线程都被阻塞

一个线程从数据读取时,请求读取的其它线程允许继续执行,但请求写入的线程仍被阻塞。

向线程写入的线程结束后,要么解除一个写入线程的阻塞,使它能向数据写入,要么解除所有读取线程的阻塞,使它们能并发读取数据。如果没有线程被阻塞,锁就进入可以自由使用的状态,可供下一个reader或writer线程获取。

从数据读取所有线程结束后,一个writer线程被解除阻塞,使它能向数据写入。如果没有线程被阻塞,锁就进入可以自由使用的状态,可供下一个reader或writer线程获取。

它的特点:

根据以上特点有EnterReadLock和EnterWriteLock两种玩法,两种玩法跟之前的那些例子都类似,只是效果不同,这里就不举例了。

虽然提供了这么多同步构造,且玩法也很多。但是最重要的还是一点:能尽量避免就避免阻塞线程,否则应尽量使用Volatile和Interlocked方法,因为它们速度快,然而这两个只能操作简单类型。

一定要阻塞,就可以使用Monitor类,也可以用ReaderWriterLockSlim类,虽然比Monitor慢,但是允许多个线程并发进行,提升了总体性能,减少阻塞线程的几率。

用System.Lazy类或者System.Threading.LazyInitializer类去替代双检索玩法。

一句话解决这个点:

LazyStrings=newLazyString(()=DateTime.Now.ToLongTimeString(),true);

调用的话就用s.Value,实际上就是封装了双检索,有些地方加了些优化。目的就是延时加载。

异步锁

其实叫异步的同步构造,因为一般的同步构造都是用阻塞线程或者自旋来完成,而异步锁的目的就是为了不阻塞来玩。

SemaphoreSlim类的WaitAsync方法就是这个思路,信号量玩法而已。

而reader-writer语义的玩法是ConcurrentExclusiveSchedulerPair类。(当没有ConcurrentScheduler任务时,使用ExclusiveScheduler为独占式运行。没有ExclusiveScheduler运行时,ConcurrentScheduler调度的任务可同时进行)

并发集合类

FCL自带四个线程安全的集合类,全在System.Collections.Concurrent(Concurrent为并发的意思)命名空间中定义。

它们是ConcurrentQueue,ConcurrentStack,ComcurrentDictionary和ConcurrentBag。

所有这些都是“非阻塞“的。(实际上在ConcurrentQueue,ConcurrentStack和ConcurrentBag为空的时候还要提取数据,那么提取数据的这个线程就会被阻塞)

—END—


转载请注明原文网址:http://www.helimiaopu.com/bbqb/7830.html

------分隔线----------------------------