zoukankan      html  css  js  c++  java
  • Monitor.Wait初探(6)

    再来加深一下印象,每一个Object实例都维护一个SyncBlock并通过这个玩意来进行线程的同步,所以Monitor.Wait最终走到这个BOOL SyncBlock::Wait(INT32 timeOut, BOOL exitContext)并不足奇。在SyncBlock内部我们维护了一个所有正在等待此同步索引块的线程的队列,那具体是通过什麽来控制的呢,通过阅读SyncBlock::Wait源码,我们知道SyncBlock内部的这个维护链表就是SLink       m_Link;

    // We thread two different lists through this link.  When the SyncBlock is
    // active, we create a list of waiting threads here.  When the SyncBlock is
    // released (we recycle them), the SyncBlockCache maintains a free list of
    // SyncBlocks here.
    //
    // We can't afford to use an SList<> here because we only want to burn
    // space for the minimum, which is the pointer within an SLink.
    SLink       m_Link;

    在SyncBlock::Wait中通过调用ThreadQueue::EnqueueThread把当前线程的WaitEventLink加入到SyncBlock的m_Link之中:

    // Enqueue is the slow one.  We have to find the end of the Q since we don't
    // want to burn storage for this in the SyncBlock.
    /* static */
    inline void ThreadQueue::EnqueueThread(WaitEventLink *pWaitEventLink, SyncBlock *psb)
    {
        LEAF_CONTRACT;
        COUNTER_ONLY(GetPrivatePerfCounters().m_LocksAndThreads.cQueueLength++);

        _ASSERTE (pWaitEventLink->m_LinkSB.m_pNext == NULL);

        SyncBlockCache::LockHolder lh(SyncBlockCache::GetSyncBlockCache());

        SLink       *pPrior = &psb->m_Link;

        while (pPrior->m_pNext)
        {
            // We shouldn't already be in the waiting list!
            _ASSERTE(pPrior->m_pNext != &pWaitEventLink->m_LinkSB);

            pPrior = pPrior->m_pNext;
        }
        pPrior->m_pNext = &pWaitEventLink->m_LinkSB;
    }

    通过分析Thread的结构,我们知道Thread的两个私有字段:

    // For Object::Wait, Notify and NotifyAll, we use an Event inside the
    // thread and we queue the threads onto the SyncBlock of the object they
    // are waiting for.
    CLREvent        m_EventWait;
    WaitEventLink   m_WaitEventLink;

    WaitEventLink是一个struct用来管理线程等待的事件,而CLREvent        m_EventWait显然就是当前用来阻塞线程或者线程用来等待的事件对象:

    // Used inside Thread class to chain all events that a thread is waiting for by Object::Wait
    struct WaitEventLink {
        SyncBlock      *m_WaitSB;
        CLREvent       *m_EventWait;
        Thread         *m_Thread;       // Owner of this WaitEventLink.
        WaitEventLink  *m_Next;         // Chain to the next waited SyncBlock.
        SLink           m_LinkSB;       // Chain to the next thread waiting on the same SyncBlock.
        DWORD           m_RefCount;     // How many times Object::Wait is called on the same SyncBlock.
    };

    再返回到BOOL SyncBlock::Wait(INT32 timeOut, BOOL exitContext)

    我们看到刚开始就需要检查是否已经有线程在等待本SyncBlock,方法就是:

    // Does this thread already wait for this SyncBlock?
       WaitEventLink *walk = pCurThread->WaitEventLinkForSyncBlock(this);

    若果已经有了,引用数加1:

    // Wait on the same lock again.
    walk->m_Next->m_RefCount ++;

    如没有,则属于第一次,需要先创建一个事件对象CLREvent,创建过程:

    // First time this thread is going to wait for this SyncBlock.
           CLREvent* hEvent;
           if (pCurThread->m_WaitEventLink.m_Next == NULL) {
               hEvent = &(pCurThread->m_EventWait);
           }
           else {
               hEvent = GetEventFromEventStore();
           }

    而这个事件对最后真正用来WaitForMultipleObjects的那个句柄至关重要。为什麽这麽说,我们继续看SyncBlock::Wait最后调用了pCurThread->Block(timeOut, &syncState);

    // Called out of SyncBlock::Wait() to block this thread until the Notify occurs.
    BOOL Thread::Block(INT32 timeOut, PendingSync *syncState)
    {
        WRAPPER_CONTRACT;

        _ASSERTE(this == GetThread());

        // Before calling Block, the SyncBlock queued us onto it's list of waiting threads.
        // However, before calling Block the SyncBlock temporarily left the synchronized
        // region.  This allowed threads to enter the region and call Notify, in which
        // case we may have been signalled before we entered the Wait.  So we aren't in the
        // m_WaitSB list any longer.  Not a problem: the following Wait will return
        // immediately.  But it means we cannot enforce the following assertion:
    //    _ASSERTE(m_WaitSB != NULL);

        return (Wait(syncState->m_WaitEventLink->m_Next->m_EventWait, timeOut, syncState) != WAIT_OBJECT_0);
    }

    这时候又紧接着调用了Wait(syncState->m_WaitEventLink->m_Next->m_EventWait, timeOut, syncState),第一个参数明显就是刚才的CLREvent,

    // Return whether or not a timeout occured.  TRUE=>we waited successfully
    DWORD Thread::Wait(CLREvent *pEvent, INT32 timeOut, PendingSync *syncInfo)
    {
        WRAPPER_CONTRACT;

        DWORD   dwResult;
        DWORD   dwTimeOut32;

        _ASSERTE(timeOut >= 0 || timeOut == INFINITE_TIMEOUT);

        dwTimeOut32 = (timeOut == INFINITE_TIMEOUT
                       ? INFINITE
                       : (DWORD) timeOut);

        dwResult = pEvent->Wait(dwTimeOut32, TRUE /*alertable*/, syncInfo);

        // Either we succeeded in the wait, or we timed out
        _ASSERTE((dwResult == WAIT_OBJECT_0) ||
                 (dwResult == WAIT_TIMEOUT));

        return dwResult;
    }

    而最后真正的Wait还是发生在CLREvent内部,看看它的Wait:

    DWORD CLREvent::Wait(DWORD dwMilliseconds, BOOL alertable, PendingSync *syncState)
    {
        WRAPPER_CONTRACT;
        return WaitEx(dwMilliseconds, alertable?WaitMode_Alertable:WaitMode_None,syncState);
    }

    再往下看就和之前的重复了,但是这里我们要着重的地方是CLREvent的私有字段

    HANDLE m_handle;

    其实你会发现这才是最后调用WaitForMupltipleObjectEx函数需要的那个句柄对象,而它就封装在CLREvent之中,这里的Handle就代表一个内核事件对象,

    那麽那麽!这里的WaitForMupltipleObjectEx在什麽情况下返回呢?对的,需要事件对象的Set之后才能返回,ok,现在再让我们回忆一下Monitor.Wait在什麽

    时候返回,没错,就是需要在其它的线程中调用Monitor.Pulse之后才能返回,这个Pulse名字起得很形象。由此,我们自然能推断出Pulse最后其实只不过是Event.Set,现在让我们看看Pulse:

    void SyncBlock::Pulse()
    {
        CONTRACTL
        {
            INSTANCE_CHECK;
            NOTHROW;
            GC_NOTRIGGER;
            MODE_ANY;
        }
        CONTRACTL_END;

        WaitEventLink  *pWaitEventLink;

        if ((pWaitEventLink = ThreadQueue::DequeueThread(this)) != NULL)
            pWaitEventLink->m_EventWait->Set();
    }

    看到这段代码,我们再对照Monitor.Pulse的描述:从队列中取到排在最前面的线程,这里其实等价于取到那个线程的Event事件对象并Set之,由此一来,正在WaitForMupltipeObjects这个事件的线程将获得释放,对于有多个线程等待同一个Event的情况,究竟是哪个线程会被释放,还应该取决于线程的优先级等属性,但是anyway,这样的调度过程已经交给操作系统定夺了。

    同理PulseAll:

    void SyncBlock::PulseAll()
    {
        CONTRACTL
        {
            INSTANCE_CHECK;
            NOTHROW;
            GC_NOTRIGGER;
            MODE_ANY;
        }
        CONTRACTL_END;

        WaitEventLink  *pWaitEventLink;

        while ((pWaitEventLink = ThreadQueue::DequeueThread(this)) != NULL)
            pWaitEventLink->m_EventWait->Set();
    }

  • 相关阅读:
    终于有人把MYSQL索引讲清楚了
    计算机基础知识总结与操作系统 PDF 下载
    java电子书python电子书等PDF下载方式
    redis布隆过滤器
    IntelliJ IDEA 2020.1 激活教程,亲测可用
    Another Redis DeskTop Manager一款稳定全新的redis连接工具
    一文吃透redis持久化,妈妈再也不担心我面试过不了!
    ES6 运算符
    If-Else的5种方法从入门到高级示例
    ES6中 的类(class)
  • 原文地址:https://www.cnblogs.com/dancewithautomation/p/2416378.html
Copyright © 2011-2022 走看看