C# Monitor.Wait() 源碼追蹤 (轉載)

source:

釋放對象上的鎖並阻止當前線程,直到它重新獲取該鎖。 如果已用指定的超時時間間隔,則線程進入就緒隊列。 可以在等待之前退出同步上下文的同步域,隨後重新獲取該域。

[SecuritySafeCritical]
public static bool Wait(object obj, int millisecondsTimeout, bool exitContext)
{
    if (obj == null)
    {
        throw new ArgumentNullException("obj");
    }
    return ObjWait(exitContext, millisecondsTimeout, obj);
}

[MethodImpl(MethodImplOptions.InternalCall), SecurityCritical]
private static extern bool ObjWait(bool exitContext, int millisecondsTimeout, object obj);

clr/src/vm/ecall.cpp

FCFuncStart(gMonitorFuncs)
    FCFuncElement("Enter", JIT_MonEnter)
    FCFuncElement("Exit", JIT_MonExit)
    FCFuncElement("TryEnterTimeout", JIT_MonTryEnter)
    FCFuncElement("ObjWait", ObjectNative::WaitTimeout)
    FCFuncElement("ObjPulse", ObjectNative::Pulse)
    FCFuncElement("ObjPulseAll", ObjectNative::PulseAll)
    FCFuncElement("ReliableEnter", JIT_MonReliableEnter)
FCFuncEnd()

映射到ObjectNative的方法

clr/src/vm/comobject.cpp -- cpp存儲實現

FCIMPL3(FC_BOOL_RET, ObjectNative::WaitTimeout, CLR_BOOL exitContext, INT32 Timeout, Object* pThisUNSAFE)
{
    CONTRACTL
    {
        MODE_COOPERATIVE;
        DISABLED(GC_TRIGGERS);  // can't use this in an FCALL because we're in forbid gc mode until we setup a H_M_F.
        SO_TOLERANT;
        THROWS;
    }
    CONTRACTL_END;

    BOOL retVal = FALSE;
    OBJECTREF pThis = (OBJECTREF) pThisUNSAFE;
    HELPER_METHOD_FRAME_BEGIN_RET_1(pThis);
    //-[autocvtpro]-------------------------------------------------------

    if (pThis == NULL)
        COMPlusThrow(kNullReferenceException, L"NullReference_This");

    if ((Timeout < 0) && (Timeout != INFINITE_TIMEOUT))
        COMPlusThrowArgumentOutOfRange(L"millisecondsTimeout", L"ArgumentOutOfRange_NeedNonNegNum");

    retVal = pThis->Wait(Timeout, exitContext);

    //-[autocvtepi]-------------------------------------------------------
    HELPER_METHOD_FRAME_END();
    FC_RETURN_BOOL(retVal);
}
FCIMPLEND

現在我們看到函數體中最終調用的是pThis->Wait,pThis是個啥玩意呢,通過分析代碼,發現它就是WaitTimeOut函數的最後一個參數Object* pThisUNSAFE的一個引用,原來是一個Object類型,那這裏的Object和c#的object或者.Net的Object有啥關係,大膽猜想,這其實就是託管Object對應的native Object。而事實也應如此。

那麼廢話不多說,我們要來看看此Object的Wait實現,依然避免不了搜索一番,首先我們在object.h中找到了Object類的定義,摘取其說明如下,也印證了剛纔的猜想:

/*
 * Object
 *
 * This is the underlying base on which objects are built.   The MethodTable
 * 這是構建對象的基礎。的方法表
 * 每個對象都要維護自己的方法表
 * 
 * pointer and the sync block index live here.  The sync block index is actually
 * 指針和同步塊索引在這裏。同步塊索引實際上是 
 * at a negative offset to the instance.  See syncblk.h for details.
 * *在實例的負偏移量處。詳見syncbl .h。
 *
 */

查看wait方法:

BOOL Wait(INT32 timeOut, BOOL exitContext) 
{ 
    WRAPPER_CONTRACT; 
    return GetHeader()->Wait(timeOut, exitContext); 
}

哦,原來是先調用了GetHeader方法獲取對象頭,然後調用對象頭的Wait方法,追下去,GetHeader方法的實現:

// Sync Block & Synchronization services

// Access the ObjHeader which is at a negative offset on the object (because of 
// cache lines) 
ObjHeader   *GetHeader() 
{ 
    LEAF_CONTRACT; 
    return PTR_ObjHeader(PTR_HOST_TO_TADDR(this) - sizeof(ObjHeader)); 
}

看來要想往下追,還必須看對象頭ObjHeader類的Wait方法實現:在syncblk.h中找到了其定義,在對應的cpp文件中找到了其相應的實現如下:

BOOL ObjHeader::Wait(INT32 timeOut, BOOL exitContext) 
{ 
    CONTRACTL 
    { 
        INSTANCE_CHECK; 
        THROWS; 
        GC_TRIGGERS; 
        MODE_ANY; 
        INJECT_FAULT(COMPlusThrowOM();); 
    } 
    CONTRACTL_END;

    //  The following code may cause GC, so we must fetch the sync block from 
    //  the object now in case it moves. 
    SyncBlock *pSB = GetBaseObject()->GetSyncBlock();

    // GetSyncBlock throws on failure 
    _ASSERTE(pSB != NULL);

    // make sure we own the crst 
    if (!pSB->DoesCurrentThreadOwnMonitor()) 
        COMPlusThrow(kSynchronizationLockException);

#ifdef _DEBUG 
    Thread *pThread = GetThread(); 
    DWORD curLockCount = pThread->m_dwLockCount; 
#endif

    BOOL result = pSB->Wait(timeOut,exitContext);

    _ASSERTE (curLockCount == pThread->m_dwLockCount);

    return result; 
}

看到了嘛!!!!該Wait實現最重要的兩行代碼終於浮現出來了,它們就是加橫線的兩行。

第一行 SyncBlock *pSB = GetBaseObject()->GetSyncBlock(); 用來獲取對象的索引塊;

第二行 BOOL result = pSB->Wait(timeOut,exitContext); 嗯,越來越接近真相,原來又調用了索引塊對象的Wait方法。

那繼續吧,看看SyncBlock 類型的Wait方法實現,依舊在syncblk.cpp中,如下:

// We maintain two queues for SyncBlock::Wait. 
// 1. Inside SyncBlock we queue all threads that are waiting on the SyncBlock. 
//    When we pulse, we pick the thread from this queue using FIFO. 
// 2. We queue all SyncBlocks that a thread is waiting for in Thread::m_WaitEventLink. 
//    When we pulse a thread, we find the event from this queue to set, and we also 
//    or in a 1 bit in the syncblock value saved in the queue, so that we can return 
//    immediately from SyncBlock::Wait if the syncblock has been pulsed. 
BOOL SyncBlock::Wait(INT32 timeOut, BOOL exitContext) 
{ 
    CONTRACTL 
    { 
        INSTANCE_CHECK; 
        THROWS; 
        GC_TRIGGERS; 
        MODE_ANY; 
        INJECT_FAULT(COMPlusThrowOM()); 
    } 
    CONTRACTL_END;

    Thread  *pCurThread = GetThread(); 
    BOOL     isTimedOut = FALSE; 
    BOOL     isEnqueued = FALSE; 
    WaitEventLink waitEventLink; 
    WaitEventLink *pWaitEventLink;

    // As soon as we flip the switch, we are in a race with the GC, which could clean 
    // up the SyncBlock underneath us -- unless we report the object. 
    _ASSERTE(pCurThread->PreemptiveGCDisabled());

    // Does this thread already wait for this SyncBlock? 
    WaitEventLink *walk = pCurThread->WaitEventLinkForSyncBlock(this); ✨🤔😀😎✨
    if (walk->m_Next) { 
        if (walk->m_Next->m_WaitSB == this) { 
            // Wait on the same lock again. 
            walk->m_Next->m_RefCount ++; 
            pWaitEventLink = walk->m_Next; 
        } 
        else if ((SyncBlock*)(((DWORD_PTR)walk->m_Next->m_WaitSB) & ~1)== this) { 
            // This thread has been pulsed.  No need to wait. 
            return TRUE; 
        } 
    } 
    else { 
        // 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(); ✨🤔😀😎✨
        } 
        waitEventLink.m_WaitSB = this; 
        waitEventLink.m_EventWait = hEvent; 
        waitEventLink.m_Thread = pCurThread; 
        waitEventLink.m_Next = NULL; 
        waitEventLink.m_LinkSB.m_pNext = NULL; 
        waitEventLink.m_RefCount = 1; 
        pWaitEventLink = &waitEventLink; 
        walk->m_Next = pWaitEventLink;

        // Before we enqueue it (and, thus, before it can be dequeued), reset the event 
        // that will awaken us. 
        hEvent->Reset();

        // This thread is now waiting on this sync block 
        ThreadQueue::EnqueueThread(pWaitEventLink, this);✨🤔😀😎✨

        isEnqueued = TRUE; 
    }

    _ASSERTE ((SyncBlock*)((DWORD_PTR)walk->m_Next->m_WaitSB & ~1)== this);

    PendingSync   syncState(walk);

    OBJECTREF     obj = m_Monitor.GetOwningObject();

    m_Monitor.IncrementTransientPrecious();

    GCPROTECT_BEGIN(obj); 
    { 
        GCX_PREEMP();

        // remember how many times we synchronized 
        syncState.m_EnterCount = LeaveMonitorCompletely(); 
        _ASSERTE(syncState.m_EnterCount > 0);

        Context* targetContext = pCurThread->GetContext(); 
        _ASSERTE(targetContext); 
        Context* defaultContext = pCurThread->GetDomain()->GetDefaultContext(); 
        _ASSERTE(defaultContext);

        if (exitContext && 
            targetContext != defaultContext) 
        { 
            Context::MonitorWaitArgs waitArgs = {timeOut, &syncState, &isTimedOut}; 
            Context::CallBackInfo callBackInfo = {Context::MonitorWait_callback, (void*) &waitArgs}; 
            Context::RequestCallBack(CURRENT_APPDOMAIN_ID, defaultContext, &callBackInfo); 
        } 
        else 
        { 
            isTimedOut = pCurThread->Block(timeOut, &syncState); ✨🤔😀😎✨
        } 
    } 
    GCPROTECT_END(); 
    m_Monitor.DecrementTransientPrecious();

    return !isTimedOut; 
}

拜託,當你看到函數又臭又長的時候..尤其時還不熟悉的時候,一定要看函數的描述,該函數開頭之前的函數說明解釋了兩件事情:

1.在SyncBlock 內部維護了一個等待所有這個SyncBlock 的線程隊列,當調用pulse的時候(如Monitor.Pulse)會從該隊列取出下一個線程,方式是先進先出。

2.使用另外一個隊列維護所有有線程正在waiting的SyncBlock ,隊列類型爲WaitEventLink(也即是Thread::m_WaitEventLink的類型),一旦有pulse調用,會從該隊列取出一個Event並set.

現在再來看函數代碼部分,重點看橫線的代碼行:

WaitEventLink *walk = pCurThread->WaitEventLinkForSyncBlock(this); 

先檢查當前線程是否已經在等待對象的同步索引塊,本示例中顯然是第一次,然後通過

hEvent = &(pCurThread->m_EventWait);或者
hEvent = GetEventFromEventStore();獲取一個等待事件對象
之後會走 ThreadQueue::EnqueueThread(pWaitEventLink, this);

從而把當前線程加入到等待隊列,這時候我的腦海中又想起來MSDN上對Monitor.Wait的描述:

當線程調用 Wait 時,它釋放對象的鎖並進入對象的等待隊列。 對象的就緒隊列中的下一個線程(如果有)獲取鎖並擁有對對象的獨佔使用。

這下大概能對上號了吧。

在函數最後,還是調用了isTimedOut = pCurThread->Block(timeOut, &syncState);以實現實現當前線程的等待(或曰阻塞)。

所以依舊要看看這個Block方法的實現:

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

Block又調用了Thread的Wait方法:

// 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又調用了pEvent的Wait方法,注意這裏的pEvent是CLREvent類型,而該參數的值則是之前在SyncBlock::Wait獲取的等待事件對象。這裏我們可以大膽猜測CLREvent對應的其實是一個內核事件對象。

CLREvent的Wait實現如下,有點長,看關鍵的橫線代碼行:

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

緊接着WaitEx的實現如下:

DWORD CLREvent::WaitEx(DWORD dwMilliseconds, WaitMode mode, PendingSync *syncState) 
{ 
    BOOL alertable = (mode & WaitMode_Alertable)!=0; 
    CONTRACTL 
    { 
        if (alertable) 
        { 
            THROWS;               // Thread::DoAppropriateWait can throw   
        } 
        else 
        { 
            NOTHROW; 
        } 
        if (GetThread()) 
        { 
            if (alertable) 
                GC_TRIGGERS; 
            else 
                GC_NOTRIGGER; 
        } 
        else 
        { 
            DISABLED(GC_TRIGGERS);        
        } 
        SO_TOLERANT; 
        PRECONDITION(m_handle != INVALID_HANDLE_VALUE); // Handle has to be valid 
    } 
    CONTRACTL_END;

    _ASSERTE(Thread::AllowCallout());

    Thread *pThread = GetThread();    
#ifdef _DEBUG 
    // If a CLREvent is OS event only, we can not wait for the event on a managed thread 
    if (IsOSEvent()) 
        _ASSERTE (!pThread); 
#endif 
    _ASSERTE (pThread || !g_fEEStarted || dbgOnly_IsSpecialEEThread());

    if (IsOSEvent() || !CLRSyncHosted()) { 
        if (pThread && alertable) { 
            DWORD dwRet = WAIT_FAILED; 
            BEGIN_SO_INTOLERANT_CODE_NOTHROW (pThread, return WAIT_FAILED;); 
            dwRet = pThread->DoAppropriateWait(1, &m_handle, FALSE, dwMilliseconds, ✨🤔😀😎✨
                                              mode, ✨🤔😀😎✨
                                              syncState); ✨🤔😀😎✨
            END_SO_INTOLERANT_CODE; 
            return dwRet; 
        } 
        else { 
            _ASSERTE (syncState == NULL); 
            return CLREventWaitHelper(m_handle,dwMilliseconds,alertable); 
        } 
    } 
    else {    
       if (pThread && alertable) { 
            DWORD dwRet = WAIT_FAILED; 
            BEGIN_SO_INTOLERANT_CODE_NOTHROW (pThread, return WAIT_FAILED;); 
            dwRet = pThread->DoAppropriateWait(IsAutoEvent()?HostAutoEventWait:HostManualEventWait, ✨🤔😀😎✨
                                              m_handle,dwMilliseconds, ✨🤔😀😎✨
                                              mode, ✨🤔😀😎✨
                                              syncState); ✨🤔😀😎✨
            END_SO_INTOLERANT_CODE; 
            return dwRet; 
        } 
        else { 
            _ASSERTE (syncState == NULL); 
            DWORD option = 0; 
            if (alertable) { 
                option |= WAIT_ALERTABLE; 
            } 
            if (IsAutoEvent()) { 
                return HostAutoEventWait((IHostAutoEvent*)m_handle,dwMilliseconds, option); 
            } 
            else { 
                return HostManualEventWait((IHostManualEvent*)m_handle,dwMilliseconds, option); 
            } 
        } 
    }    
}

這裏又調用了Thread的DoAppropriateWait;
DoAppropriateWait的實現如下:

DWORD Thread::DoAppropriateWait(int countHandles, HANDLE *handles, BOOL waitAll, 
                                DWORD millis, WaitMode mode, PendingSync *syncState) 
{ 
    STATIC_CONTRACT_THROWS; 
    STATIC_CONTRACT_GC_TRIGGERS;

    INDEBUG(BOOL alertable = (mode & WaitMode_Alertable) != 0;); 
    _ASSERTE(alertable || syncState == 0);

    DWORD dwRet = (DWORD) -1;

    EE_TRY_FOR_FINALLY { 
        dwRet =DoAppropriateWaitWorker(countHandles, handles, waitAll, millis, mode); ✨🤔😀😎✨
    } 
    EE_FINALLY { 
        if (syncState) { 
            if (!GOT_EXCEPTION() && 
                dwRet >= WAIT_OBJECT_0 && dwRet < (DWORD)(WAIT_OBJECT_0 + countHandles)) { 
                // This thread has been removed from syncblk waiting list by the signalling thread 
                syncState->Restore(FALSE); 
            } 
            else 
                syncState->Restore(TRUE); 
        }

        _ASSERTE (dwRet != WAIT_IO_COMPLETION); 
    } 
    EE_END_FINALLY;

    return(dwRet); 
}

then,DoAppropriateWaitWorker的實現如下,有點長,只看最關鍵那一句:

DWORD Thread::DoAppropriateWaitWorker(int countHandles, HANDLE *handles, BOOL waitAll, 
                                      DWORD millis, WaitMode mode) 
{ 
    CONTRACTL { 
        THROWS; 
        GC_TRIGGERS; 
    } 
    CONTRACTL_END;

    DWORD ret = 0;

    BOOL alertable = (mode & WaitMode_Alertable)!= 0; 
    BOOL ignoreSyncCtx = (mode & WaitMode_IgnoreSyncCtx)!= 0;

    // Unless the ignoreSyncCtx flag is set, first check to see if there is a synchronization 
    // context on the current thread and if there is, dispatch to it to do the wait. 
    // If  the wait is non alertable we cannot forward the call to the sync context 
    // since fundamental parts of the system (such as the GC) rely on non alertable 
    // waits not running any managed code. Also if we are past the point in shutdown were we 
    // are allowed to run managed code then we can't forward the call to the sync context. 
    if (!ignoreSyncCtx && alertable && CanRunManagedCode(FALSE)) 
    { 
        GCX_COOP();

        BOOL fSyncCtxPresent = FALSE; 
        OBJECTREF SyncCtxObj = NULL; 
        GCPROTECT_BEGIN(SyncCtxObj) 
        { 
            GetSynchronizationContext(&SyncCtxObj); 
            if (SyncCtxObj != NULL) 
            { 
                SYNCHRONIZATIONCONTEXTREF syncRef = (SYNCHRONIZATIONCONTEXTREF)SyncCtxObj; 
                if (syncRef->IsWaitNotificationRequired()) 
                { 
                    fSyncCtxPresent = TRUE; 
                    ret = DoSyncContextWait(&SyncCtxObj, countHandles, handles, waitAll, millis); 
                } 
            } 
        } 
        GCPROTECT_END();

        if (fSyncCtxPresent) 
            return ret; 
    }

    GCX_PREEMP();

    if(alertable) 
    { 
        DoAppropriateWaitWorkerAlertableHelper(mode); 
    }

    LeaveRuntimeHolder holder((size_t)WaitForMultipleObjectsEx); 
    StateHolder<MarkOSAlertableWait,UnMarkOSAlertableWait> OSAlertableWait(alertable);

    ThreadStateHolder tsh(alertable, TS_Interruptible | TS_Interrupted);

    ULONGLONG dwStart = 0, dwEnd; 
retry: 
    if (millis != INFINITE) 
    { 
        dwStart = CLRGetTickCount64(); 
    }

    ret = DoAppropriateAptStateWait(countHandles, handles, waitAll, millis, mode);✨🤔😀😎✨

    if (ret == WAIT_IO_COMPLETION) 
    { 
        _ASSERTE (alertable);

        if (m_State & TS_Interrupted) 
        { 
            HandleThreadInterrupt(mode & WaitMode_ADUnload); 
        } 
        // We could be woken by some spurious APC or an EE APC queued to 
        // interrupt us. In the latter case the TS_Interrupted bit will be set 
        // in the thread state bits. Otherwise we just go back to sleep again. 
        if (millis != INFINITE) 
        { 
            dwEnd = CLRGetTickCount64(); 
            if (dwEnd >= dwStart + millis) 
            { 
                ret = WAIT_TIMEOUT; 
                goto WaitCompleted; 
            } 
            else 
            { 
                millis -= (DWORD)(dwEnd - dwStart); 
            } 
        } 
        goto retry; 
    } 
    _ASSERTE((ret >= WAIT_OBJECT_0  && ret < (WAIT_OBJECT_0  + (DWORD)countHandles)) || 
             (ret >= WAIT_ABANDONED && ret < (WAIT_ABANDONED + (DWORD)countHandles)) || 
             (ret == WAIT_TIMEOUT) || (ret == WAIT_FAILED)); 
    // countHandles is used as an unsigned -- it should never be negative. 
    _ASSERTE(countHandles >= 0);

    if (ret == WAIT_FAILED) 
    { 
        DWORD errorCode = ::GetLastError(); 
        if (errorCode == ERROR_INVALID_PARAMETER) 
        { 
            if (CheckForDuplicateHandles(countHandles, handles)) 
                COMPlusThrow(kDuplicateWaitObjectException); 
            else 
                COMPlusThrowHR(HRESULT_FROM_WIN32(errorCode)); 
        } 
        else if (errorCode == ERROR_ACCESS_DENIED) 
        { 
            // A Win32 ACL could prevent us from waiting on the handle. 
            COMPlusThrow(kUnauthorizedAccessException); 
        }

        _ASSERTE(errorCode == ERROR_INVALID_HANDLE);

        if (countHandles == 1) 
            ret = WAIT_OBJECT_0; 
        else if (waitAll) 
        { 
            // Probe all handles with a timeout of zero. When we find one that's 
            // invalid, move it out of the list and retry the wait. 
#ifdef _DEBUG 
            BOOL fFoundInvalid = FALSE; 
#endif 
            for (int i = 0; i < countHandles; i++) 
            { 
                // WaitForSingleObject won't pump memssage; we already probe enough space 
                // before calling this function and we don't want to fail here, so we don't 
                // do a transition to tolerant code here 
                DWORD subRet = WaitForSingleObject (handles[i], 0); 
                if (subRet != WAIT_FAILED) 
                    continue; 
                _ASSERTE(::GetLastError() == ERROR_INVALID_HANDLE); 
                if ((countHandles - i - 1) > 0) 
                    memmove(&handles[i], &handles[i+1], (countHandles - i - 1) * sizeof(HANDLE)); 
                countHandles--; 
#ifdef _DEBUG 
                fFoundInvalid = TRUE; 
#endif 
                break; 
            } 
            _ASSERTE(fFoundInvalid);

            // Compute the new timeout value by assume that the timeout 
            // is not large enough for more than one wrap 
            dwEnd = CLRGetTickCount64(); 
            if (millis != INFINITE) 
            { 
                if (dwEnd >= dwStart + millis) 
                { 
                    ret = WAIT_TIMEOUT; 
                    goto WaitCompleted; 
                } 
                else 
                { 
                    millis -= (DWORD)(dwEnd - dwStart); 
                } 
            } 
            goto retry; 
        } 
        else 
        { 
            // Probe all handles with a timeout as zero, succeed with the first 
            // handle that doesn't timeout. 
            ret = WAIT_OBJECT_0; 
            int i; 
            for (i = 0; i < countHandles; i++) 
            { 
            TryAgain: 
                // WaitForSingleObject won't pump memssage; we already probe enough space 
                // before calling this function and we don't want to fail here, so we don't 
                // do a transition to tolerant code here 
                DWORD subRet = WaitForSingleObject (handles[i], 0); 
                if ((subRet == WAIT_OBJECT_0) || (subRet == WAIT_FAILED)) 
                    break; 
                if (subRet == WAIT_ABANDONED) 
                { 
                    ret = (ret - WAIT_OBJECT_0) + WAIT_ABANDONED; 
                    break; 
                } 
                // If we get alerted it just masks the real state of the current 
                // handle, so retry the wait. 
                if (subRet == WAIT_IO_COMPLETION) 
                    goto TryAgain; 
                _ASSERTE(subRet == WAIT_TIMEOUT); 
                ret++; 
            } 
            _ASSERTE(i != countHandles); 
        } 
    }

WaitCompleted:

    _ASSERTE((ret != WAIT_TIMEOUT) || (millis != INFINITE));

    return ret; 
}

then, 還要看 DoAppropriateAptStateWait(countHandles, handles, waitAll, millis, mode)的實現:

DWORD Thread::DoAppropriateAptStateWait(int numWaiters, HANDLE* pHandles, BOOL bWaitAll, 
                                         DWORD timeout, WaitMode mode) 
{ 
    CONTRACTL { 
        THROWS; 
        GC_TRIGGERS; 
    } 
    CONTRACTL_END;

    BOOL alertable = (mode&WaitMode_Alertable)!=0;

    return WaitForMultipleObjectsEx_SO_TOLERANT(numWaiters, pHandles,bWaitAll, timeout,alertable); 
}

then,再看WaitForMultipleObjectsEx_SO_TOLERANT的實現:

DWORD WaitForMultipleObjectsEx_SO_TOLERANT (DWORD nCount, HANDLE *lpHandles, BOOL bWaitAll,DWORD dwMilliseconds, BOOL bAlertable) 
{ 
    DWORD dwRet = WAIT_FAILED; 
    DWORD lastError = 0; 
    BEGIN_SO_TOLERANT_CODE (GetThread ()); 
    dwRet = ::WaitForMultipleObjectsEx (nCount, lpHandles, bWaitAll, dwMilliseconds, bAlertable); 
    lastError = ::GetLastError(); 
    END_SO_TOLERANT_CODE;

    // END_SO_TOLERANT_CODE overwrites lasterror.  Let's reset it. 
    ::SetLastError(lastError); 
    return dwRet; 
}

到這裏,萬水千山,我們終於搞清楚Monitor.Wait的大概實現原理(事實上我們只捋了一遍本文示例中Monitor.Enter的調用stack),內部最終還是調用了WaitForMultipleObjectsEx,不過要注意CLREvent::WaitEx的實現有好幾個分支,根據情況的不同,最後調的並不一定是WaitForMultipleObjectsEx,也有可能是CLREventWaitHelper->WaitForSingleObjectEx等等。


轉載

再來加深一下印象,每一個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(); 
}

轉載

現在我們再回到最初的示例上來,ThreadProc1和ThreadProc2之間通過lock關鍵字進行同步,加在在這兩個線程上的lock就好比兩扇大門,而這兩扇門同時只允許打開一扇。我們先在第一個線程中打開了第一扇門,那第二個線程就要在第二扇門外徘徊。而要打開第二扇門就應該等待第一扇門的Monitor.Exit,Exit的調用就好比是關上當前的門,通知另外的門可以打開了。

但是現在似乎出了點”意外“。

但是現在第一扇門打開之後,突然蹦出個Monitor.Wait,這玩意是個人物,它除了讓第一個線程處於阻塞狀態,還通知第二扇門可以打開了。這也就是說:並不需要等到第一扇門調用Monitor.Exit,第二扇門就可以打開了。

這一切究竟是怎麼發生的?帶着種種疑惑,我們慢慢來撥開雲霧見青天。

還需要從BOOL SyncBlock::Wait(INT32 timeOut, BOOL exitContext)開頭,

該函數在真正的Block當前線程也即是調用isTimedOut = pCurThread->Block(timeOut, &syncState)之前,有一行代碼值得研究一番:

syncState.m_EnterCount = LeaveMonitorCompletely();

單看這行代碼所調用的函數名稱,直譯成:徹底離開Monitor,聽起來和Monitor.Exit有點異曲同工之妙。

再來看看其實現:

LONG LeaveMonitorCompletely() 
{ 
    WRAPPER_CONTRACT; 
    return m_Monitor.LeaveCompletely(); 
}

嗯,又調用了

m_Monitor.LeaveCompletely();
這個m_Monitor在SyncBlock類中的定義:

protected: 
   AwareLock  m_Monitor;                    // the actual monitor

 

註釋說這是實際的Monitor,所以我們應該能猜出這就是Monitor.Enter/Exit所涉及的類(事實上也是如此,因爲我很快看到了Monitor.Enter對應的實現就是AwareLock.Enter),是一個AwareLock 的變量。

Ok,我們再來看AwareLock 的LeaveCompletely實現:

LONG AwareLock::LeaveCompletely() 
{ 
    WRAPPER_CONTRACT;

    LONG count = 0; 
    while (Leave()) { 
        count++; 
    } 
    _ASSERTE(count > 0);            // otherwise we were never in the lock

    return count; 
}

再看Leave:

BOOL AwareLock::Leave() 
{ 
    CONTRACTL 
    { 
        INSTANCE_CHECK; 
        NOTHROW; 
        GC_NOTRIGGER; 
        MODE_ANY; 
    } 
    CONTRACTL_END;

    Thread* pThread = GetThread();

    AwareLock::LeaveHelperAction action = LeaveHelper(pThread);

    switch(action) 
    { 
    case AwareLock::LeaveHelperAction_None: 
        // We are done 
        return TRUE; 
    case AwareLock::LeaveHelperAction_Signal: 
        // Signal the event 
        Signal(); 
        return TRUE; 
    default: 
        // Must be an error otherwise 
        _ASSERTE(action == AwareLock::LeaveHelperAction_Error); 
        return FALSE; 
    } 
}

由此可以看出所謂徹底離開不過就是遍歷+Signal();那麼這個Signal函數究竟做了啥,看名字和註釋知其一二:Signal the event

void    Signal() 
{ 
    WRAPPER_CONTRACT; 
    // CLREvent::SetMonitorEvent works even if the event has not been intialized yet 
    m_SemEvent.SetMonitorEvent(); 
}

現在問題又來了,m_SemEvent是啥?首先,定義:

CLREvent        m_SemEvent;

是個CLREvent,然後看看其初始化,是在void AwareLock::AllocLockSemEvent()中:

m_SemEvent.CreateMonitorEvent((SIZE_T)this);

啊哈,只看名字就知道這一個Monitor專用的Event,那麼AllocLockSemEvent又被誰調用呢,是BOOL AwareLock::EnterEpilog(Thread* pCurThread, INT32 timeOut),而EnterEpilog又爲AwareLock::Enter所調用,事實上當EnterEpilog就是第二扇門的徘迴函數。我們來看看怎麼徘徊的:

for (;;) 
       { 
           // We might be interrupted during the wait (Thread.Interrupt), so we need an 
           // exception handler round the call. 
           EE_TRY_FOR_FINALLY 
           { 
               // Measure the time we wait so that, in the case where we wake up 
               // and fail to acquire the mutex, we can adjust remaining timeout 
               // accordingly. 
               start = CLRGetTickCount64(); 
              ret = m_SemEvent.Wait(timeOut, TRUE); 
               _ASSERTE((ret == WAIT_OBJECT_0) || (ret == WAIT_TIMEOUT)); 
               if (timeOut != (INT32) INFINITE) 
               { 
                   end = CLRGetTickCount64(); 
                   if (end == start) 
                   { 
                       duration = 1; 
                   } 
                   else 
                   { 
                       duration = end - start; 
                   } 
                   duration = min(duration, (DWORD)timeOut); 
                   timeOut -= (INT32)duration; 
               } 
           }

要注意關鍵行

ret = m_SemEvent.Wait(timeOut, TRUE); 下文還會講到。這明顯是在等待事件對象的信號有狀態。

再來看看SetMonitorEvent的實現:

void CLREvent::SetMonitorEvent() 
{ 
    CONTRACTL 
    { 
        NOTHROW; 
        GC_NOTRIGGER; 
    } 
    CONTRACTL_END;

    // SetMonitorEvent is robust against initialization races. It is possible to 
    // call CLREvent::SetMonitorEvent on event that has not been initialialized yet by CreateMonitorEvent. 
    // CreateMonitorEvent will signal the event once it is created if it happens.

    for (;;) 
    { 
        LONG oldFlags = m_dwFlags;

        if (oldFlags & CLREVENT_FLAGS_MONITOREVENT_ALLOCATED) 
        { 
            // Event has been allocated already. Use the regular codepath. 
            Set(); 
            break; 
        }

        LONG newFlags = oldFlags | CLREVENT_FLAGS_MONITOREVENT_SIGNALLED; 
        if (FastInterlockCompareExchange((LONG*)&m_dwFlags, newFlags, oldFlags) != oldFlags) 
        { 
            // We lost the race 
            continue; 
        } 
        break; 
    } 
}

又調用了Set函數:

BOOL CLREvent::Set() 
{ 
    CONTRACTL 
    { 
      NOTHROW; 
      GC_NOTRIGGER; 
      PRECONDITION((m_handle != INVALID_HANDLE_VALUE)); 
    } 
    CONTRACTL_END;

    _ASSERTE(Thread::AllowCallout());

    if (IsOSEvent() || !CLRSyncHosted()) { 
        return UnsafeSetEvent(m_handle); 
    } 
    else { 
        if (IsAutoEvent()) { 
            HRESULT hr; 
            BEGIN_SO_TOLERANT_CODE_CALLING_HOST(GetThread()); 
            hr = ((IHostAutoEvent*)m_handle)->Set(); 
            END_SO_TOLERANT_CODE_CALLING_HOST; 
            return hr == S_OK; 
        } 
        else { 
            HRESULT hr; 
            BEGIN_SO_TOLERANT_CODE_CALLING_HOST(GetThread()); 
            hr = ((IHostManualEvent*)m_handle)->Set(); 
            END_SO_TOLERANT_CODE_CALLING_HOST; 
            return hr == S_OK; 
        } 
    } 
}

在Set函數中我們看到最終是對m_handle的Set。從而使得事件狀態被置成有信號狀態,也即釋放了所有的lock而使得它們重新處於被調度狀態。

現在再回過頭來看看AwareLock::EnterEpilog的邏輯,已經知道是通過ret = m_SemEvent.Wait(timeOut, TRUE)等待事件對象的信號狀態,而我麼也已經知道在調用Monitor.Wait之後會調用事件對象的Set函數從而使得等待的線程得到鎖。那麼爲了加深印象,我還想通過Windbg走走。


https://github.com/SSCLI/sscli20_20060311

https://www.cnblogs.com/dancewithautomation/archive/2012/03/25/2416260.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章