1. 程式人生 > 實用技巧 >golang執行緒排程模型詳解

golang執行緒排程模型詳解

常用執行緒排程模型

1、N:1,其中幾個使用者空間執行緒在一個OS執行緒上執行。這樣做的優點是可以非常快速地進行上下文切換,但不能利用多核系統的優勢。
2、1:1,即一個執行執行緒與一個OS執行緒匹配。它利用了機器上的所有核心,但是上下文切換很慢,因為它必須通過作業系統進行捕獲。
3、M:N,也是Go目前使用的。它將任意數量的goroutines排程到任意數量的OS執行緒上。您可以快速切換上下文,並利用系統中的所有核心。這種方法的主要缺點是它增加了排程器的複雜性。

Golang執行緒排程模型詳解

為了完成排程任務,Go排程器使用了3個主要實體:
在這裡插入圖片描述
三角形表示一個OS執行緒。它是由作業系統管理的執行執行緒,工作方式與標準POSIX執行緒非常相似。在執行時程式碼中,它被稱為M表示機器。

圓圈代表goroutine。它包括堆疊,指令指標和其他重要的資訊排程goroutines,就像任何通道可能被阻塞。在執行時程式碼中,它被稱為G。
矩形表示排程上下文。您可以將它看作是在單個執行緒上執行Go程式碼的排程器的本地化版本。這是讓我們從N:1排程器到M:N排程器的重要部分。在執行時程式碼中,它被稱為P表示處理器。
在這裡插入圖片描述
這裡我們看到兩個執行緒(M),每個執行緒持有一個上下文( P ),每個執行緒執行一個goroutine ( G )。為了執行goroutine,一個執行緒必須持有一個上下文。
上下文的數量在啟動時設定為GOMAXPROCS環境變數的值,或者通過執行時函式GOMAXPROCS()設定。通常情況下,這在程式執行期間不會改變。上下文的數量是固定的,這意味著只有GOMAXPROCS在任何時候都在執行Go程式碼。我們可以使用它來調優對單個計算機的Go程序呼叫,比如在4核的PC上執行4個執行緒的Go程式碼。
灰色的goroutines沒有執行,但準備被安排。它們被安排在稱為runqueue的列表中。每當goroutine執行go語句時,goroutine都會被新增到執行佇列的末尾。一旦上下文在排程點之前運行了goroutine,它就會從執行佇列中彈出一個goroutine,設定堆疊和指令指標,並開始執行goroutine。
為了減少互斥鎖爭用,每個上下文都有自己的本地執行佇列。Go排程器的前一個版本只有一個全域性執行佇列,並有一個互斥鎖保護它。執行緒經常被阻塞,等待互斥鎖被解鎖。當你有32個核心的機器,你想要儘可能地提高效能時,這就變得很糟糕了。
只要所有上下文都有goroutines執行,排程器就會在這種穩定狀態下繼續進行排程。然而,有兩種情況可以改變這種情況。

一、sycall呼叫時

現在你可能會想,為什麼要有上下文呢?難道我們不能把執行佇列放線上程上,並擺脫上下文嗎?不是真的。我們使用上下文的原因是,如果正在執行的執行緒由於某種原因需要阻塞,我們可以將它們傳遞給其他執行緒。
我們需要阻塞的一個例子是呼叫系統呼叫。由於一個執行緒不能在系統呼叫中同時執行程式碼和被阻塞,所以我們需要傳遞上下文,這樣它才能保持排程。
在這裡插入圖片描述
這裡我們看到一個執行緒放棄了它的上下文,以便另一個執行緒可以執行它。排程程式確保有足夠的執行緒執行所有上下文。上面插圖中的M1可能只是為了處理這個系統呼叫而建立的,或者它可能來自執行緒快取。系統呼叫執行緒將保持生成系統呼叫的goroutine,因為它在技術上仍然在執行,儘管在作業系統中被阻塞了。
當系統呼叫返回時,執行緒必須嘗試獲取上下文,以便執行返回的goroutine。通常的操作模式是從其他執行緒中竊取上下文。如果它不能竊取一個,它將把goroutine放到一個全域性執行佇列中,把自己放到執行緒快取中,然後進入睡眠狀態。
全域性執行佇列是上下文在執行完其本地執行佇列時從其拉出的執行佇列。上下文還會定期檢查全域性執行佇列中的goroutines。否則,全域性執行佇列上的goroutines可能會因為飢餓而永遠不會執行。
這種對系統呼叫的處理是Go程式執行多個執行緒的原因,即使是在GOMAXPROCS為1時也是如此。執行時使用goroutines來呼叫系統呼叫,而將執行緒留在後面。

二、偷取工作任務

系統穩定狀態改變的另一種方式是當上下文沒有goroutines可排程時。如果上下文執行佇列上的工作量不平衡,就會發生這種情況。這可能導致上下文耗盡它的執行佇列,而系統中仍有工作要做。為了繼續執行Go程式碼,上下文可以將goroutines從全域性執行佇列中取出,但如果其中沒有goroutines,它將不得不從其他地方獲取它們。
在這裡插入圖片描述
這是另一種情況。當一個上下文用完時,它會嘗試從另一個上下文偷取大約一半的執行佇列。這可以確保在每個上下文上都有工作要做,從而確保所有執行緒都在最大容量下工作。

排程程式還有很多細節,比如cgo執行緒、LockOSThread()函式以及與網路輪詢器的整合。