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();
    }

  • 相关阅读:
    171 01 Android 零基础入门 03 Java常用工具类02 Java包装类 01 包装类简介 01 Java包装类内容简介
    170 01 Android 零基础入门 03 Java常用工具类01 Java异常 08 Java异常总结 01 异常总结
    169 01 Android 零基础入门 03 Java常用工具类01 Java异常 07 异常链 01 异常链简介
    168 01 Android 零基础入门 03 Java常用工具类01 Java异常 06 自定义异常 01 自定义异常类
    167 01 Android 零基础入门 03 Java常用工具类01 Java异常 05 使用throw和throws实现异常处理 02 使用throw抛出异常对象
    166 01 Android 零基础入门 03 Java常用工具类01 Java异常 05 使用throw和throws实现异常处理 01 使用throws声明异常类型
    165 01 Android 零基础入门 03 Java常用工具类01 Java异常 04 使用try…catch…finally实现异常处理 05 return关键字在异常处理中的使用
    DevExpress WPF v20.2版本亮点放送:全新升级的PDF Viewer
    界面控件DevExpress使用教程:Dashboard – 自定义导出
    DevExpress WinForms帮助文档:表单控件
  • 原文地址:https://www.cnblogs.com/dancewithautomation/p/2416378.html
Copyright © 2011-2022 走看看