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