Golang的協程調度

調度的基礎,模型關係的映射

GPM模型:

  • G,Goroutinue
    • 被調度器管理的輕量級線程,goroutine使用go關鍵字創建
    • 調度系統的最基本單位goroutine,存儲了goroutine的執行stack信息、goroutine狀態以及goroutine的任務函數等。默認的大小是2KB,根據需要逐步上漲。
    • G綁定到P上執行
  • P,Processor
    • 邏輯執行單元
    • 存儲了M執行的上下文,包括各種G對象隊列、鏈表、cache和狀態
    • G存在於P中的特定鏈表上,同一時刻,P只能在一個M上,因此不需要鎖
  • M,Machine
    • 操作系統的實際的線程
    • OS的執行的單位,Linux下的大小是8MB
    • M綁定執行的P,但是不保存P的狀態,因此P可以跨M執行

整體的調度關係

每個M都有一個P,綠色的G表示當前M上運行的G,灰色的表示local G queue

從上圖理解出,P是G實際的運行綁定的單位,因此P的數量決定了可以併發的G數量;又因爲P最終是綁定到M上的,因此M的數量決定了最終的並行的數量。在Golang中,P的數量由runtime.GOMAXPROCS來控制,M取決於硬件,默認情況下,等於OS的線程數量。

P的隊列空了之後,不會一直閒置,而是會從其它P中或者全局G queue中,如下圖:

關於Global G Queue和每個P的Local G Queue產生方式:
go關鍵字生成一個G,之後G會嘗試放入當前的P的Local G Queue中,如果失敗了,就放入Global G Queue。

如果G發生阻塞,則會嘗試尋找新的G來運行,阻塞的G返回後重新加入G Queue中。

P在輪詢查找G的時候,每隔61次從Global G Queue中查找,保證Global也可以執行。當一個G執行超過10ms時,schedule會有對應的搶佔機制。

一些底層知識

線程切換與協程切換的區別。LTS(Local Thread Storage)存儲了線程執行需要的堆棧信息,寄存器的數據等,之後線程會load 程序並執行。對於執行中的進程,在對應的地址其實位置,同樣會啓動線程,此時OS會分配對應的內存空間,並啓動執行。Linux系統中,1個線程是啓動的大小8MB,而且啓動和上下文切換會消耗對應的時間。

協程的特點,協程不是OS級別的,因此協程的功能是程序內部調度的。OS感覺不到協程的存在,因爲OS根本就沒有協程的概念!!!

Golang爲協程的代碼段,在堆上分配初始化的2KB的空間,之後進入之前提到的調度流程。一般來說,Golang使用了線程複用的方式,即啓動線程的時候,在上面有協程運行,協程停止或者阻塞的時候,不會主動停止線程,而是更改線程的FS寄存器的值到對應的協程代碼段上,然後此時線程執行的位置就是新的協程的代碼位置了,此時協程切換的代價是改變線程執行的位置,然後執行新的協程,因此代價很小。

這邊可能要後期更正,FS寄存器那邊的概念不是特別清楚

具體調度方案

給出整體的調度狀態切換圖:

sysmon:搶佔式調度系統,對於執行時間超過10ms的G,會更正爲可搶佔的,其他協程可以搶佔該G的執行,防止被一個G一直佔用。

以下幾種情況會導致Goroutinue阻塞,進而讓出P,使得P與其它G綁定,高效利用CPU:

  • syscall:系統調用,比如讀寫長文本等
  • Network IO:網絡傳輸數據等
  • channel獲取不到數據
  • sync包的調用

參考資料:

  • https://zboya.github.io/post/go_scheduler/
  • https://blog.csdn.net/u010853261/article/details/84790392#Section1_Scheduler_9
  • http://www.sizeofvoid.net/goroutine-under-the-hood/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章