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

    现在我们再回到最初的示例上来,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走走。

  • 相关阅读:
    第二次冲刺spring会议(第一次会议)
    团队项目(4.15站立会议)
    团队项目(4.14站立会议)
    VB中的GDI编程-1 设备环境DC
    合并多个表格数据的代码
    随机跳转页面之使用VBA公共变量
    快速找到Office应用程序安装路径
    CSS3学习笔记(3)-CSS3边框
    CSS3学习笔记(2)-CSS盒子模型
    测试一下js是否可用
  • 原文地址:https://www.cnblogs.com/dancewithautomation/p/2416641.html
Copyright © 2011-2022 走看看