上一章讲了基元线程同步构造,而其它的线程同步构造都是基于这些基元线程同步构造的,并且一般都合并了用户模式和内核模式构造,我们称之为混合线程同步构造。
在没有线程竞争时,混合线程提供了基于用户模式构造所具备的性能优势,而多个线程竞争一个构造时,混合线程通过基元内核模式的构造来提供不“自旋”的优势。
那么接下来就是个简单的混合线程同步构造的例子,可与上一章最后的那些例子相比较:
public class SimpleHybridLock : IDisposable { private Int32 m_waiters = 0; private AutoResetEvent m_waiterlock = new AutoResetEvent(false);//注意这里是false public void Enter() { if (Interlocked.Increment(ref m_waiters)==1) { return; } m_waiterlock.WaitOne(); } public void Leave() { if (Interlocked.Decrement(ref m_waiters) == 0) { return; } m_waiterlock.Set(); } public void Dispose() { m_waiterlock.Dispose(); } }
上面的例子学了上一张后看起来感觉很简单就不讲解了,只是一个简单的,将Interlocked这种互锁构造和自动重置事件构造AutoResetEvent 结合起来的,混合线程同步构造的例子。
上面混合锁可以去加入自旋,当超过一定的自旋次数时再进行阻塞。也可以去加入互斥体的递归玩法,总之这个东西充满了无限的可能。
.NET 框架类库中的混合构造
总体而言,实际上就是对上面那个简单例子的扩展,它们的目的都是为了使线程能尽可能不去进入内核模式,并且减少线程竞争时自旋的性能影响。
虽然提供了这么多同步构造,且玩法也很多。但是最重要的还是一点:能尽量避免就避免阻塞线程,否则应尽量使用Volatile和Interlocked方法,因为它们速度快,然而这两个只能操作简单类型。
一定要阻塞,就可以使用Monitor类,也可以用ReaderWriterLockSlim类,虽然比Monitor慢,但是允许多个线程并发进行,提升了总体性能,减少阻塞线程的几率。
用System.Lazy类或者System.Threading.LazyInitializer类去替代双检索玩法。
一句话解决这个点:
Lazy<String> s=new Lazy<String>(()=>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为空的时候还要提取数据,那么提取数据的这个线程就会被阻塞)