這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
Golang中實現協程調度演算法的主要有以下三個資料結構,正是這三個結構加上一些演算法構成了Golang的協程調度演算法,當然,這些資料結構也是在不斷進化的,保不準未來又會加入其他結構來提升調度器效能。
協程調度主要資料結構
其中:
M: 代表作業系統線程,也就是我們經常理解的線程,是真正參與OS調度的單元,每個goroutine只有依附於某個M方可真正地執行;
G: 代表協程,也就是我們經常使用的Goroutine;
P: 協程調度器的基本調度單元,G的容身之所,串連G和M的橋樑。
理解調度器的關鍵是理解P的作用:它是實現M:N調度的關鍵結構。在Golang1.1版本中被引入,解決了1.0中的全域調度隊列帶來的可擴充性問題。
三個資料結構之間的關係如下:
- 每個G都必須依附於某個P;
- 每個M想啟動並執行時候,必須先擷取一個P,再執行P中的G;
- 每個P維護可運行G隊列,避免了全域G隊列帶來的鎖競爭問題,提高了調度器的可擴充性;
- 如果一個M在執行G的時候進入了syscall,那麼該M就被佔據了,與其關聯的P此時就游離出來,可被同樣處於idle狀態的M擷取到,進而P內的G得到繼續執行的機會;
- go中可通過GOMAXPROCS()來設定P的數量,一般建議設定成系統核心數即可,P的數量也代表了當前活躍的M數(因進入syscall而block的M不計算在內)。
struct G { ...... m *m}struct M { ...... G* curg; // current running goroutine P* p; // attached P for executing Go code (nil if not executing Go code) ......}struct P { ...... M* m; // back-link to associated M (nil if idle) ...... // Queue of runnable goroutines. uint32 runqhead; uint32 runqtail; G* runq[256]; // Available G's (status == Gdead) G* gfree; ......};
閱讀上面的資料結構可發現他們三者之間的關係是這樣的:
- g:有一個指標指向執行它的m,也即g隸屬於m;
- m:儲存了當前正在執行的g,同時還指向p,也即m隸屬於p;
- p: 儲存了當前正執行p內g的m,同時還有屬於它的g的鏈表