成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

聊一聊 Monitor.Wait 和 Pluse 的底層玩法

開發(fā) 前端
在dump分析的過程中經(jīng)常會看到很多線程卡在Monitor.Wait?方法上,曾經(jīng)也有不少人問我為什么用 !syncblk? 看不到 Monitor.Wait 上的鎖信息,剛好昨天有時間我就來研究一下。

一、背景

1. 講故事

在dump分析的過程中經(jīng)常會看到很多線程卡在Monitor.Wait方法上,曾經(jīng)也有不少人問我為什么用 !syncblk 看不到 Monitor.Wait 上的鎖信息,剛好昨天有時間我就來研究一下。

二、Monitor.Wait 底層怎么玩的

1. 案例演示

為了方便講述,先上一段演示代碼,Worker1 在執(zhí)行的過程中需要喚醒 Worker2 執(zhí)行,當(dāng) Worker2 執(zhí)行完畢之后自己再繼續(xù)執(zhí)行,參考代碼如下:

internal class Program
    {
        static Person lockObject = new Person();

        static void Main()
        {
            Task.Run(() => { Worker1(); });
            Task.Run(() => { Worker2(); });

            Console.ReadLine();
        }

        static void Worker1()
        {
            lock (lockObject)
            {
                Console.WriteLine($"{DateTime.Now} 1. 執(zhí)行 worker1 的業(yè)務(wù)邏輯...");
                Thread.Sleep(1000);

                Console.WriteLine($"{DateTime.Now} 2. 等待 worker2 執(zhí)行完畢...");
                Monitor.Wait(lockObject);

                Console.WriteLine($"{DateTime.Now} 4. 繼續(xù)執(zhí)行 worker1 的業(yè)務(wù)邏輯...");
            }
        }

        static void Worker2()
        {
            Thread.Sleep(10);
            lock (lockObject)
            {
                Console.WriteLine($"{DateTime.Now} 3. worker2 的邏輯執(zhí)行完畢...");
                Monitor.Pulse(lockObject);
            }
        }
    }

    public class Person { }

圖片圖片

有了代碼和輸出之后,接下來就是分析底層玩法了。

2. 模型架構(gòu)圖

研究來研究去總得有個結(jié)果,千言萬語繪成一張圖,截圖如下:

圖片圖片

從圖中可以看到這地方會涉及到一個核心的數(shù)據(jù)結(jié)構(gòu) WaitEventLink,參考如下:

// Used inside Thread class to chain all events that a thread is waiting for by Object::Wait
struct WaitEventLink {
    SyncBlock         *m_WaitSB;    // 當(dāng)前對象的 syncblock
    CLREvent          *m_EventWait;    // 當(dāng)前線程的 m_EventWait 
    PTR_Thread         m_Thread;       // Owner of this WaitEventLink.
    PTR_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.
};

代碼里對每一個字段都做了表述,還是非常清楚的,也看到了這里存在兩個隊列。

  1. m_Next: 當(dāng)前線程要串聯(lián)的 SyncBlock 隊列,Node 是 WaitEventLink 結(jié)構(gòu)。
  2. m_LinkSB:當(dāng)前同步塊串聯(lián)的 Thread 隊列,Node 是 m_LinkSB 地址。

3. 底層的源碼驗證

首先我們看下C#的 Monitor.Wait(lockObject) 底層是如何實現(xiàn)的,它對應(yīng)著 coreclr 的 ObjectNative::WaitTimeout 方法,核心實現(xiàn)如下:

BOOL SyncBlock::Wait(INT32 timeOut)
{
 //步驟1
    WaitEventLink* walk = pCurThread->WaitEventLinkForSyncBlock(this);

 //步驟2
    CLREvent* hEvent = &(pCurThread->m_EventWait);

    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;

    hEvent->Reset();

 //步驟3
    ThreadQueue::EnqueueThread(pWaitEventLink, this);

    isEnqueued = TRUE;
    PendingSync syncState(walk);

    OBJECTREF obj = m_Monitor.GetOwningObject();

    m_Monitor.IncrementTransientPrecious();

 //步驟4
    syncState.m_EnterCount = LeaveMonitorCompletely();

    isTimedOut = pCurThread->Block(timeOut, &syncState);

    return !isTimedOut;
}

代碼邏輯非常簡單,大概步驟如下:

  1. 從當(dāng)前線程的 m_WaitEventLink 所指向的隊列中尋找 SyncBlock 節(jié)點,如果沒有就返回尾部節(jié)點。
  2. 將當(dāng)前節(jié)點拼接到尾部。
  3. 新節(jié)點通過 EnqueueThread 方法送入到 m_LinkSB 所指向的隊列,這里有一個小技巧,它只存放 WaitEventLink->m_LinkSB 地址,后續(xù)會通過 -0x20 來反推 WaitEventLink 結(jié)構(gòu)首地址,從而來獲取線程等待事件,參考代碼如下:
inline PTR_WaitEventLink ThreadQueue::WaitEventLinkForLink(PTR_SLink pLink)
{
    LIMITED_METHOD_CONTRACT;
    SUPPORTS_DAC;
    return (PTR_WaitEventLink) (((PTR_BYTE) pLink) - offsetof(WaitEventLink, m_LinkSB));
}
  1. 使用 LeaveMonitorCompletely 方法將 AwareLock 鎖給釋放掉,從而讓等待這個 lock 的線程進(jìn)入方法,即當(dāng)前的 Worker2,簡化后代碼如下:
LONG LeaveMonitorCompletely()
{
    return m_Monitor.LeaveCompletely();
}

void Signal()
{
    m_SemEvent.SetMonitorEvent();
}

void CLREventBase::SetMonitorEvent(){
    Set();
}

總而言之,Monitor.Wait 主要還是用來將Node追加到兩大隊列,接下來研究下 Monitor.Pulse 的內(nèi)部實現(xiàn),這個就比較簡單了,無非就是在 m_LinkSB 指向的隊列中提取一個Node而已,核心代碼如下:

void SyncBlock::Pulse()
{
    WaitEventLink* pWaitEventLink;

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

// Unlink the head of the Q.  We are always in the SyncBlock's critical
// section.
/* static */
inline WaitEventLink *ThreadQueue::DequeueThread(SyncBlock *psb)
{
    WaitEventLink* ret = NULL;
    SLink* pLink = psb->m_Link.m_pNext;

    if (pLink)
    {
        psb->m_Link.m_pNext = pLink->m_pNext;
        ret = WaitEventLinkForLink(pLink);
    }
    return ret;
}

inline PTR_WaitEventLink ThreadQueue::WaitEventLinkForLink(PTR_SLink pLink)
{
    return (PTR_WaitEventLink)(((PTR_BYTE)pLink) - offsetof(WaitEventLink, m_LinkSB));
}

class SyncBlock
{
  protected:
    SLink m_Link;
}

上面的代碼邏輯還是非常清楚的,從 SyncBlock.m_Link 所串聯(lián)的 WaitEventLink 隊列中提取第一個節(jié)點,但這個節(jié)點保存的是 WaitEventLink.m_LinkSB 地址,所以需要反向 -0x20 取到 WaitEventLink 首地址,可以用 windbg 來驗證一下。

0:017> dt coreclr!WaitEventLink
   +0x000 m_WaitSB         : Ptr64 SyncBlock
   +0x008 m_EventWait      : Ptr64 CLREvent
   +0x010 m_Thread         : Ptr64 Thread
   +0x018 m_Next           : Ptr64 WaitEventLink
   +0x020 m_LinkSB         : SLink
   +0x028 m_RefCount       : Uint4B

取到首地址之后就就可以將當(dāng)前線程的 m_EventWait 喚醒,這就是為什么調(diào)用 Monitor.Pulse(lockObject); 之后另一個線程喚醒的內(nèi)部邏輯,有些朋友好奇那 Monitor.PulseAll 是不是會把這個隊列中的所有 Node 上的 m_EventWait 都喚醒呢?哈哈,真聰明,源碼如下:

void SyncBlock::PulseAll()
{
    WaitEventLink* pWaitEventLink;

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

眼尖的朋友會有一個疑問,這個隊列數(shù)據(jù)提取了,那另一個隊列的數(shù)據(jù)是不是也要相應(yīng)的改動,這個確實,它的邏輯是在Wait方法的 PendingSync syncState(walk); 析構(gòu)函數(shù)里,感興趣的朋友可以看一下內(nèi)部的void Restore(BOOL bRemoveFromSB) 方法即可。

三、總結(jié)

花了半天研究這東西還是挺有意思的,重點還是要理解下那張圖,理解了之后我相信你對 Monitor.Pluse 方法注釋中所指的 waiting queue 會有一個新的體會。

圖片 圖片

責(zé)任編輯:武曉燕 來源: 一線碼農(nóng)聊技術(shù)
相關(guān)推薦

2025-01-10 08:15:22

C#異步底層

2024-06-28 12:47:29

C#弱引用底層

2023-06-02 07:45:39

2017-12-26 10:19:14

大數(shù)據(jù)問題缺陷

2020-09-08 06:54:29

Java Gradle語言

2023-07-06 13:56:14

微軟Skype

2022-05-18 16:35:43

Redis內(nèi)存運維

2018-06-07 13:17:12

契約測試單元測試API測試

2023-09-22 17:36:37

2021-01-28 22:31:33

分組密碼算法

2020-05-22 08:16:07

PONGPONXG-PON

2022-02-21 15:01:45

MySQL共享鎖獨占鎖

2024-03-28 09:02:25

PythonGetattr工具

2022-06-27 08:24:34

JDKJavaJRE

2021-12-06 09:43:01

鏈表節(jié)點函數(shù)

2021-07-16 11:48:26

模型 .NET微軟

2023-09-20 23:01:03

Twitter算法

2021-03-01 18:37:15

MySQL存儲數(shù)據(jù)

2021-08-01 09:55:57

Netty時間輪中間件

2023-09-27 16:39:38

點贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 国产精品永久免费观看 | 在线中文字幕视频 | 国产蜜臀97一区二区三区 | 久久精品无码一区二区三区 | 成年人在线视频 | 国产99在线 | 欧美 | 91最新入口 | 亚洲精品视频播放 | aaa国产大片 | 精品视频在线免费观看 | 亚洲网在线| 色综合天天天天做夜夜夜夜做 | 午夜精品一区二区三区在线视 | 宅女噜噜66国产精品观看免费 | 一区二区三区四区在线视频 | 亚州成人| 国产美女在线免费观看 | 国产福利91精品一区二区三区 | 国产传媒在线观看 | 岛国av一区二区三区 | 美女天天操 | 色综合久 | 99久久国产免费 | 久久免费精品 | 国产影音先锋 | 插插插干干干 | 99热这里都是精品 | 国产精品久久久久久久久动漫 | 精品免费在线 | 华丽的挑战在线观看 | 成人在线免费 | 激情一区二区三区 | 欧美一级欧美一级在线播放 | 亚洲综合一区二区三区 | 日韩在线观看一区 | 久久一区二区精品 | 亚洲成人播放器 | 农村真人裸体丰满少妇毛片 | 亚洲视频免费在线观看 | 在线免费av电影 | 国产一二区视频 |