天天看點

Goroutine 排程模型猜想

前言

近日在學習go語言,go語言的一個重大特色就是支援協程(coroutine),即使用者級線程。由運作在使用者态程式實作“執行體”的排程和切換(本文将一個可并發執行的邏輯單元稱為“執行體”),整個過程運作在一個或多個線程上,執行體切換過程不用“陷入”核心态,是以較為輕量。

這種方式也有一定的缺點,因為協程概念提出的很早,主流語言卻沒有提供支援,必有原因,這個我們以後讨論。

當然,為了更好的了解goroutine,我們就有必要談談goroutine排程模型的實作。不過,作者是java開發人員,對分析c及彙編源碼之類的事情能力有限,雖然有很多參考資料及公開的源碼,分析來分析去,我也是醉了。是以在看了一些goroutine scheduler資料後,本文從另一個角度切入,描述下一個簡單的協程排程模型如何實作。最後的分析可能與goroutine實際的實作不太一緻,還望大家見諒。

單線程上的協程排程模型

假設隻有一個線程,該線程運作一個排程程式,排程程式從其管理的執行體集合中取出一個執行體交給線程執行。該線程執行邏輯如下:

Goroutine 排程模型猜想

如果考慮執行體各種非正常退出的情況:

Goroutine 排程模型猜想

由此我們可以确定,實作該線程邏輯需要兩個抽象:執行體和排程程式。

  • 執行體包含:執行體的上下文環境,執行邏輯等
  • 排程程式:執行體集合(連結清單),執行體切換等

該模型有兩個問題:

  • 除非執行體執行完畢、主動放棄或調用系統調用,執行體将一直“占用”線程。排程器因為沒有“占用”線程,也隻能幹看着。這種非搶占式方式是協程排程模型的通病。
  • 執行體執行過程中可能向執行體集合添加新的執行體,導緻該線程負擔過大。

多個線程上協程排程模型

可以想見,如果隻是運作在一個線程上,那麼goroutine也太簡單了,接下來談談運作在多個線程上的情況。

不管是運作在幾個線程上,線程總得一個一個啟動,下圖是第一個線程的運作邏輯。當然,在第一個線程啟動前,肯定要為整個排程模型做一些資料結構的初始化工作。

Goroutine 排程模型猜想

多個線程上排程跟單個線程排程有一個很大的不同就是:一旦執行體連結清單包含的執行體過多,就需要開啟新的線程,轉移部分執行體。反之,如果某個線程執行體連結清單為空,則需要從全局連結清單或其它線程那裡“挖”執行體。

執行體本身是有狀态的。

  • 主動放棄執行。将執行體設定為runnable狀态,然後放入到全局執行體集合。(看來執行體主動放棄執行,不僅是放棄所線上程的執行機會,還不想在本線程繼續呆了)
  • channel操作和網絡操作等。執行體被置為wating狀态,從本線程執行體集合中剝離,直到其依賴的條件滿足。
  • 調用系統調用。

第一個線程開始執行後,go排程模型還會建立一個sysmon線程(負責監控,不運作執行體)還有一個問題,那就是一旦執行體中調用系統調用,整個線程會被阻塞。為了不影響線程中其它執行體的執行,Go語言完全是自己封裝的系統調用,是以在封裝系統調用的時候,可以做不少手腳,也就是進入系統調用的時候執行entersyscall,退出後又執行exitsyscall函數。entersyscall将執行體從目前執行體連結清單中剝離,并将改變P的狀态為syscall。Sysmon監控線程會掃描所有的P,發現一個P處于了syscall的狀态,就知道這個P遇到了執行體在做系統調用,于是系統監控線程就會建立一個新的線程去把這個處于syscall的P給搶過來,開始幹活,這樣這個小車中的其它執行體就可以繞過之前系統調用的等待了。被搶走P的線程等系統調用傳回後,發現自己的P沒了,不能繼續幹活了,于是隻能把執行系統調用的執行體放回到全局連結清單中,自己睡覺去了。

goroutine排程模型的四個抽象

有了上述鋪墊,我們再來看goroutine排程模型4個重要結構,分别是M、G、P、Sched,前三個定義在runtime.h中,Sched定義在proc.c中。

  • Sched結構就是排程器,它維護有存儲M和G的隊列(可能是全局的)以及排程器的一些狀态資訊等。
  • M代表核心級線程,一個M就是一個線程,goroutine就是跑在M之上的;M是一個很大的結構,裡面維護小對象記憶體cache(mcache)、目前執行的goroutine、随機數發生器等等非常多的資訊。
  • P全稱是Processor,處理器,它的主要用途就是用來執行goroutine的,是以它也維護了一個goroutine隊列,裡面存儲了所有需要它來執行的goroutine。
  • G就是goroutine實作的核心結構了,G維護了goroutine需要的棧、程式計數器以及它所在的M等資訊。

這樣從前到後談下來,是不是更順一些。

參考文獻

goroutine與排程器

go