混合线程同步构造
定义:通过合并用户模式和内核模式的构造来构建的,称为混合线程同步构造(hybrid thread synchronization construct).在没有线程竞争的时候,混合构造提供了基于用户模式构造所具有的性能上的优势。在有多个线程同时竞争一个构造的时候,混合构造还使用基于内核模式的构造来提供不“自旋”的优势。
由于在大多数应用程序中,线程都很少同时竞争一个构造,所以性能上的增强可以使你的应用程序表现得更出色。
一个简单的混合锁:
internal sealed class SimpleHybridLock : IDisposable{ // Int32由基元用户模式构造(Interlocked的方法)使用 private Int32 m_waiters=0; // AutoResetEvent是基元内核模式构造 private AutoResetEvent m_waitLock = new AutoResetEvent(false); public void Enter(){ if(Interlocked.Increment(ref m_waiters)==1) { return; //此时锁可自由使用,无竞争,直接返回 } //另一个线程正在等待。这代表一个竞争,因此阻塞这个线程 m_waitLock.WaitOne();//这里产生较大的性能影响 //WaitOne返回后,这个线程现在拥有了锁 } public void Leave(){ if(Interlocked.Increment(ref m_waiters)==0) { return;//不存在需要唤醒的线程了,直接返回 } //有其它线程正在阻塞,唤醒其中一个 m_waitLock.Set();//这里产生较大的性能影响 } public void Dispose(){ m_waitLock.Dispose(); } }
SimpleHybridLock包含两个字段:一个 Int32,它将通过基元用户模式的构造来操作;以及一个AutoResetEvent,它是一个基于内核模式的构造.为了获得出色的性能,锁尽量使用Int32,尽量避免使用AutoResetEvent.只是构造一个SimpleHybridLock对象,就会导致AutoResetEvent的创建;相较于Int32字段所产生的开销,这会使性能造成较大的损失。
SimpleHybridLock解释:
调用Enter的第一个线程造成Interlocked.Increment在m_waiters字段上加1,使它的值变成1.这个线程发现以前有零个线程正在等待这个锁,所以线程从它的Enter调用中返回。值得欣赏的是,线程获得锁的速度是非常快的。现在如果另一个线程介入并调用Enter,这个线程将 m_waiters递增到2,发现锁在另一个线程那里。所以,这个线程会使用AutoResetEvent对象来调用WaitOne,从而阻塞自身。调用WaitOne造成线程的代码转换成内核模式的代码,这会对性能产生巨大影响。然而,线程现在会阻塞,不会因为在CPU上“自旋”而浪费CPU的时间。
调用Leave方法时,会调用Interlocked.Decrement从m_waiters字段减1.如果m_waiters现在是0,表明没有其他线程在对Enter的调用中发生阻塞,调用Leave的线程可以直接返回。同样地,想象一下这有多快:离开一个锁意味着一个线程从一个Int32中减1,执行快速的if测试,然后返回!另一方面,如果调用Leave的线程发现m_waiters不为1,线程就知道现在存在一个竞争,另外至少有一个线程在内核中阻塞。这个线程必须唤醒一个(而且只能是一个)阻塞的线程。唤醒线程是通过在AutoResetEvent上调用Set来实现的。这会造成性能上的损失,因为线程必须转换成内核模式的代码,然后又转回来。但是,只有在发生竞争是,才会发生这种转换。当然,AutoResetEvent确保只有一个阻塞的线程被唤醒;在AutoResetEvent上阻塞的其他所有线程会继续阻塞,直到新的、解除了阻塞的线程最终调用Leave。