之前讨论了基元用户模式和内核模式线程同步构造。其他所有线程同步构造都基于它们,而且一般都合并了用户模式和内核模式构造,我们称为混合线程同步构造。没有线程竞争时,混合构造提供了基元用户模式构造所具有的性能优势。多个线程竞争一个构造时,混合构造通过基元内核模式的构造来提供不自旋的优势。由于大多数应用程序的线程都很少同时竞争一个构造,所以性能上的增强可以使你的应用程序表现得更出色。

       本章最后展示如何使用fcl的并发集合类来取代混合构造,从而最小化资源使用并提升性能。同时还讨论了异步的同步构造,允许以同步方式访问资源,同时不造成任何线程的阻塞,从而减少资源消耗,并提高了伸缩性。

一个简单的混合锁

internal sealed class SimpleHybridLock:IDisposable
{
    //int由基元用户模式构造(interlocked的方法)使用
    private Int32 m_waiters = 0;

    //autoResetEvent基元内核模式构造
    private AutoResetEvent m_waiterLock = new AutoResetEvent(false);

    private void Enter()
    {
        //指出这个线程想要获得锁
        if (Interlocked.Increment(ref m_waiters) ==1)
        {
            return;//锁可以使用,无竞争,直接返回
        }
        //另一个线程用有锁(发送竞争),使这个线程等待
        m_waiterLock.WaitOne();//这里产生性能消耗,但是也比自旋要好(当然也存在自旋几毫秒就获得锁的情况。。。。)
        //waitone返回后,这个线程拿到了锁
    }
    public void Leave()
    {
        //这个线程准备释放锁
        if (Interlocked.Decrement(ref m_waiters)==0)
        {
            return;
        }
        //其他线程正在阻塞,唤醒其中一个
        m_waiterLock.Set();

    }
    public void Dispose()
    {
        m_waiterLock.Dispose();
    }
}

  SimpleHybridLock包含两字字段:一个int32,由基元用户模式的构造来操作;以及一个AutoResetEvent,他是一个基元内核模式的构造。为了获得出色的性能,锁要尽量操作int32,尽量少操作AutoResetEvent。也有出现竞争情况才去创建AutoResetEvent的设计,后面的示例代码也有这种设计。

  在实际应用中,任何线程都可以在任何时候调用leave方法,因为enter并没有记录哪个线程获得了锁。虽然可以添加字段和代码维护这个信息,但是会增大内存开销而且影响enter和leave的性能,我情愿有一个性能高超的锁,并确保我的代码以正确的方式使用它。你会注意到,事件和信号量都没有维护这种信息,只有互斥体才有维护。

自旋、线程所有权和递归

  由于转换为内核模式会造成巨大的性能损失,而且线程占有锁的时间通常很短,所以为了提升应用的总体性能,可以让一个线程在用户模式自旋一小段时间,再让线程转换为内核模式。如果线程正在等待的锁在自旋期间变得可用,就能避免模式转换了。

       此外,有的锁限制只能由获得锁的线程释放锁,有的锁允许递归,所以可以通过一些逻辑支持自旋、线程所有权和递归,下面是一个例子:

internal sealed class AnotherHybridLock:IDisposable
{
    //int由基元用户模式构造(interlocked的方法)使用
    private Int32 m_waiters = 0;

    //autoResetEvent基元内核模式构造
    private AutoResetEvent m_waiterLock = new AutoResetEvent(false);

    //这个字段控制自旋,希望能提升性能
    private Int32 m_spincount = 4000;
    //这些字段指出哪个线程拥有锁,以及拥有了多少次
    private Int32 m_owingThreadId=0,m_recursion=0;
    private void Enter()
    {
        //如果调用线程已经拥有锁,递增递归计数并返回
        Int32 threadId = Thread.CurrentThread.ManagedThreadId;
        if (threadId==m_owingThreadId)
        {
            m_recursion++;return;
        }
        //调用线程不拥有锁,尝试获取他
        SpinWait spinwait = new SpinWait();
        for (int i = 0; i < m_spincount; i++)
        {
            //锁国锁可以自由使用了,这个线程获取
            //比较location1与comparand,如果不相等,什么都不做;如果location1与comparand相等,则用value替换location1的值。无论比较结果相等与否,返回值都是location1中原有的值
            if (Interlocked.CompareExchange(ref m_waiters,1,0) == 0)
            {
                goto GotLock;
            }
            //黑科技:给其他线程运行的机会,希望锁会被释放
            spinwait.SpinOnce();
        }


        //自旋结束,再次尝试
        if (Interlocked.Increment(ref m_waiters) > 1)
        {
            //仍然是竞争状态,线程阻塞
            m_waiterLock.WaitOne();
            //等待结束之后,他拥有锁
        }
        GotLock:
        //一个线程获得锁时,记录他的id,并指出线程拥有锁一次
        m_owingThreadId = threadId;m_recursion = 1;
    }
    public void Leave()
    {
        //这个线程准备释放锁
        if (Interlocked.Decrement(ref m_waiters) == 0)
        {
            return;
        }
        //其他线程正在阻塞,唤醒其中一个
        m_waiterLock.Set();

    }
    public void Dispose()
    {
        m_waiterLock.Dispose();
    }
}
改进的混合锁

相关文章:

  • 2021-12-05
  • 2021-12-04
  • 2021-07-28
  • 2022-12-23
  • 2021-11-23
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
猜你喜欢
  • 2021-10-21
  • 2022-01-20
  • 2021-12-05
  • 2022-12-23
  • 2021-11-13
  • 2022-12-23
  • 2022-12-23
相关资源
相似解决方案