Golang 協程排程

2021-10-04 06:41:54 字數 3083 閱讀 6188

n個使用者空間執行緒在1個核心空間執行緒上執行。優勢是上下文切換非常快但是無法利用多核系統的優點,多個使用者空間執行緒無法並行執行。

1個核心空間執行緒執行乙個使用者空間執行緒。這種充分利用了多核系統的優勢但是上下文切換非常慢,因為每一次排程都會在使用者態和核心態之間切換。

每個使用者執行緒對應多個核心空間執行緒,同時也可以乙個核心空間執行緒對應多個使用者空間執行緒。go採用這種模型,使用多個核心執行緒管理多個goroutine。這樣結合了以上兩種模型的優點,但缺點就是排程的複雜性

groutine能擁有強大的併發實現是通過gpm排程模型實現,下面就來解釋下goroutine的排程模型。

m:m是對核心級執行緒的封裝數量對應真實的cpu數,乙個m就是乙個執行緒,goroutine就是跑在m之上的;

g:代表乙個goroutine,它有自己的棧,用於排程。

p:p全稱是processor,處理器,它的主要用途就是用來執行goroutine的。每個processor物件都擁有乙個lrq(local run queue)未分配的goroutine物件儲存在**grq(global run queue )**中,等待分配給某乙個p的lrq中,每個lrq裡面包含若干個使用者建立的goroutine物件。

golang採用的是m:n執行緒模型,更詳細的說他是乙個兩級執行緒模型,但它對系統執行緒(核心級執行緒)進行了封裝,暴露了乙個輕量級的協程goroutine(使用者級執行緒)供使用者使用,而使用者級執行緒到核心級執行緒的排程由golang的runtime負責,排程邏輯對外透明。goroutine的優勢在於上下文切換在完全使用者態進行,無需像執行緒一樣頻繁在使用者態與核心態之間切換,節約了資源消耗

從上圖中看,有2個物理執行緒m,每乙個m都擁有乙個處理器p,每乙個也都有乙個正在執行的goroutine

p的數量可以通過gomaxprocs()來設定,它其實也就代表了真正的併發度,即有多少個goroutine可以同時執行。

圖中灰色的那些goroutine並沒有執行,而是出於ready的就緒態,正在等待被排程。p維護著這個佇列(稱之為runqueue),

go語言裡,啟動乙個goroutine很容易:go function 就行,所以每當有乙個go語句被執行,runqueue佇列就在其末尾加入乙個goroutine,在下乙個排程點,就從runqueue中取出(如何決定取哪個goroutine?)乙個goroutine執行。

gwaitting. 內部channel或者mutex阻塞

gsyscall. 呼叫了syscall

當乙個os執行緒m0陷入阻塞時(如下圖),p轉而在執行m1,圖中的m1可能是正被建立,或者從執行緒快取中取出。

當mo返回時,它必須嘗試取得乙個p來執行goroutine,一般情況下,它會從其他的os執行緒那裡拿乙個p過來,

如果沒有拿到的話,它就把goroutine放在乙個global runqueue裡,然後自己睡眠(放入執行緒快取裡)。所有的p也會週期性的檢查global runqueue並執行其中的goroutine,否則global runqueue上的goroutine永遠無法執行。

另一種情況是p所分配的任務g很快就執行完了(分配不均),這就導致了這個處理器p很閒,但是其他的p還有任務,此時如果global runqueue沒有任務g了,那麼p不得不從其他的p裡拿一些g來執行。一般來說,如果p從其他的p那裡要拿任務的話,一般就拿run queue的一半,這就確保了每個os執行緒都能充分的使用,如下圖:

1、p的數量:

由啟動時環境變數$gomaxprocs或者是由runtime的方法

gomaxprocs()決定(預設是1)。這意味著在程式執行的任意時刻都只有$gomaxprocs個goroutine在同時執行。

2、m的數量:

go語言本身的限制:go程式啟動時,會設定m的最大數量,預設10000.但是核心很難支援這麼多的執行緒數,所以這個限制可以忽略。

runtime/debug中的setmaxthreads函式,設定m的最大數量

乙個m阻塞了,會建立新的m

m與p的數量沒有絕對關係,乙個m阻塞,p就會去建立或者切換另乙個m,所以,即使p的預設數量是1,也有可能會建立很多個m出來。

3、p何時建立:

在確定了p的最大數量n後,執行時系統會根據這個數量建立n個p。

4、m何時建立:

沒有足夠的m來關聯p並執行其中的可執行的g。比如所有的m此時都阻塞住了,而p中還有很多就緒任務,就會去尋找空閒的m,而沒有空閒的,就會去建立新的m。

m會選擇導致此m被建立的那個p關聯。

當m因系統呼叫而阻塞時(m上執行的g進入了系統呼叫的時候),m與p會分開,如果此時p的就緒佇列中還有任務,p就會去關聯乙個空閒的m,或者建立乙個m進行關聯。

預設情況下:因為p的預設數量是1(m不一定是1),所以如果我們不改變gomaxprocs,無論我們在程式中用go語句建立多少個goroutine,它們都只會被塞入同乙個p的就緒佇列中。

有多個p的情況下:如果修改了gomaxprocs或者呼叫了runtime.gomaxprocs,執行時系統會把所有的g均勻的分布在各個p的就緒佇列中。

如果乙個p的就緒佇列所有任務都執行完了,那麼p會嘗試從其他p的就緒佇列中取出一部分到自己的就緒佇列中,以保證每個p的就緒佇列都有任務可以執行

參考文章

Golang協程排程

有時候可能會出現這種情況,乙個無恥的goroutine阻止其他goroutine執行。當你有乙個不讓排程器執行的 for迴圈時,這就會發生。package main import fmt func main for done fmt.println done for迴圈並不需要是空的。只要它包含了不...

Golang 協程排程

下面看看golang的協程排程 groutine能擁有強大的併發實現是通過gpm排程模型實現,下面就來解釋下goroutine的排程模型。go的排程器內部有三個重要的結構 m,p,g m m是對核心級執行緒的封裝,數量對應真實的cpu數,乙個m就是乙個執行緒,goroutine就是跑在m之上的 m是...

golang 協程理解

本文總結一下go協程的理解,如有錯誤望請指正。網上都說協程是一種輕量級執行緒,執行緒又是一種輕量級的程序。這話在語言層面看來是沒有錯的,但它們的實現是不同的。執行緒是cpu資源排程的最小單位。協程不由cpu進行排程,由應用程式進行排程,也就是由go進行排程。在go中,協程的排程也有專門的排程器。但g...