天天看點

一文徹底弄懂go中的排程GMP

一文徹底弄懂go中的排程GMP

先說,協程的本質是使用者态的線程,使用者對其有控制權限,記憶體占用少,切換代價低。

再來解釋一下MPG是什麼意思。

M代表核心線程,所有的G都要放在M上才能運作。

P代表控制器,排程G到M上,其維護了一個隊列,存儲了所有需要它來排程的G。

G代表一個go routine單元。

補充幾點常見的排程政策:

1,如果某個M陷入阻塞呢?

當一個OS線程M由于io操作而陷入阻塞,假設此時G0正跑在了M上,那麼M上綁定的P就會帶着餘下的所有G去尋找新的M。當M恢複過來時,一般情況下,會從别的M上拿過來一個P,并把原先跑在其上的G0放到P的隊列中,進而運作G0。如果,沒有拿到可用的P的話,就把G0放入到全局global runqueue隊列中,使G0等待被排程,然後M進入線程緩存。所有的P也會周期性的檢查global runqueue并運作其中的goroutine,否則global runqueue上的goroutine永遠無法執行。

2,如果有的M較忙,有的M較閑呢?

此時P所配置設定的任務G很快就執行完了(配置設定不均),這就導緻了這個處理器P很忙,但是其他的P還有任務。此時,P首先會去global runqueue取G。但是,如果global runqueue沒有任務G了,那麼P不得不從其他的P裡拿一些G來執行。一般來說,如果P從其他的P那裡要拿任務的話,一般就拿run queue的一半,這就確定了每個OS線程都能充分的使用。

3,如果一個G運作時間過長,導緻隊列中後續G都無法運作呢?

啟動的時候,會專門建立一個線程sysmon,用來監控和管理,在内部是一個循環。首先,記錄所有P的G任務計數schedtick,schedtick會在每執行一個G任務後遞增。如果檢查到 schedtick一直沒有遞增,說明這個P一直在執行同一個G任務,如果超過一定的時間(10ms),就在這個G任務的棧資訊裡面加一個标記。然後這個G任務在執行的時候,如果遇到非内聯函數調用,就會檢查一次這個标記,然後中斷自己,把自己加到隊列末尾,執行下一個G。如果沒有遇到非内聯函數(有時候正常的小函數會被優化成内聯函數)調用的話,那就慘了,會一直執行這個G任務,直到它自己結束;如果是個死循環,并且GOMAXPROCS=1的話,恭喜你,夯住了!親測,的确如此。

4,一個G由于排程被中斷,此後如何恢複?

中斷的時候将寄存器裡的棧資訊,儲存到自己的G對象裡面。當再次輪到自己執行時,将自己儲存的棧資訊複制到寄存器裡面,這樣就接着上次之後運作了。 ~(≧▽≦)/~

繼續閱讀