go GMP
動態棧
作業系統的執行緒一般都有固定的棧記憶體(通常為2MB),而 Go 語言中的 goroutine 非常輕量級,一個 goroutine 的初始棧空間很小(一般為2KB),所以在 Go 語言中一次創建數萬個 goroutine 也是可能的。並且 goroutine 的棧不是固定的,可以根據需要動態地增大或縮小, Go 的 runtime 會自動為 goroutine 分配合適的棧空間。
goroutine調度
作業系統內核在調度時會掛起當前正在執行的執行緒並將暫存器中的內容保存到記憶體中,然後選出接下來要執行的執行緒並從記憶體中恢復該執行緒的暫存器資訊,然後恢復執行該執行緒的現場並開始執行執行緒。從一個執行緒切換到另一個執行緒需要完整的上下文切換。因為可能需要多次記憶體訪問,所以這個切換上下文的操作開銷較大,會增加運行的cpu周期。
區別於作業系統內核調度作業系統執行緒,goroutine 的調度是Go語言運行時(runtime)層面的實現,是完全由 Go 語言本身實現的一套調度系統——go scheduler。它的作用是按照一定的規則將所有的 goroutine 調度到作業系統執行緒上執行。
在經曆數個版本的迭代之後,目前 Go 語言的調度器採用的是 GPM
調度模型。
- G:表示 goroutine,每執行一次
go f()
就創建一個 G,包含要執行的函數和上下文資訊。 - 全局隊列(Global Queue):存放等待運行的 G。
- P:表示 goroutine 執行所需的資源,最多有 GOMAXPROCS 個。
- P 的本地隊列:同全局隊列類似,存放的也是等待運行的G,存的數量有限,不超過256個。新建 G 時,G 優先加入到 P 的本地隊列,如果本地隊列滿了會批量移動部分 G 到全局隊列。
- M:執行緒想運行任務就得獲取 P,從 P 的本地隊列獲取 G,當 P 的本地隊列為空時,M 也會嘗試從全局隊列或其他 P 的本地隊列獲取 G。M 運行 G,G 執行之後,M 會從 P 獲取下一個 G,不斷重複下去。
- Goroutine 調度器和作業系統調度器是通過 M 結合起來的,每個 M 都代表了1個內核執行緒,作業系統調度器負責把內核執行緒分配到 CPU 的核上執行。
單從執行緒調度講,Go語言相比起其他語言的優勢在於OS執行緒是由OS內核來調度的, goroutine 則是由Go運行時(runtime)自己的調度器調度的,完全是在用戶態下完成的, 不涉及內核態與用戶態之間的頻繁切換,包括記憶體的分配與釋放,都是在用戶態維護著一塊大的記憶體池, 不直接調用系統的malloc函數(除非記憶體池需要改變),成本比調度OS執行緒低很多。 另一方面充分利用了多核的硬體資源,近似的把若干goroutine均分在物理執行緒上, 再加上本身 goroutine 的超輕量級,以上種種特性保證了 goroutine 調度方面的性能。
GOMAXPROCS
Go運行時的調度器使用GOMAXPROCS
參數來確定需要使用多少個 OS 執行緒來同時執行 Go 程式碼。默認值是機器上的 CPU 核心數。例如在一個 8 核心的機器上,GOMAXPROCS 默認為 8。Go語言中可以通過runtime.GOMAXPROCS
函數設置當前程式並發時佔用的 CPU邏輯核心數。(Go1.5版本之前,默認使用的是單核心執行。Go1.5 版本之後,默認使用全部的CPU 邏輯核心數。)