GMP并發模型
程序與線程與協程
多個線程屬于同一個程序并共享記憶體空間,線程之間的通訊基于共享的記憶體進行。
Go語言的排程器使用與CPU數量相等的線程來排程多個Goroutine。
”為什麼用Go語言?“
程序、線程存在問題:
- CPU高消耗
- 切換線程上下文需要申請、銷毀資源消耗時間高
- 記憶體高占用
- 線程占用1M以上的記憶體空間
協程(Goroutine)的優點:
- 占用的記憶體更小(幾kb)
- 初始為2kb,如果棧空間不足則自動擴容
- 排程更靈活(runtime排程)
- Go自己實作的排程器,建立和銷毀的消耗非常小,是使用者級。
- 搶占式排程(10ms)
- 編譯器插入搶占指令,函數調用時檢查目前Goroutine是否發起搶占請求
- 1.14版本後支援基于信号的異步搶占(20ms)
- 垃圾回收掃描棧時觸發搶占排程
- 解決搶占式排程因垃圾回收和循環長時間占用資源(無法執行搶占指令)導緻程式暫停
GMP并發模型
GMP
圖-GMP
G 需要在 M 上才能運作,M 依賴 P 提供的資源,P 則持有待運作的 G,M 與 P 的數量沒有絕對關系,一個 M 阻塞,P 就會去建立或者切換另一個 M,是以,即使 P 的預設數量是 1,也有可能會建立很多個 M 出來。
G: 取 goroutine 的首字母,主要儲存 goroutine 的一些狀态資訊以及 CPU 的一些寄存器的值
M: 取 machine 的首字母,它代表一個工作線程,或者說系統線程。G 需要排程到 M 上才能運作,M 是真正工作的人
P:取 processor 的首字母,為 M 的執行提供“上下文”,儲存 M 執行 G 時的一些資源,例如本地可運作 G 隊列,memeory cache 等。
”你了解過GMP并發模型嗎?“
GM老版排程器:
- 激烈的鎖競争
- 從全局隊列中擷取G,需要加鎖
- 局部性差
- 比如當 G 中包含建立新協程的時候,M 建立了 G’,為了繼續執行 G,需要把 G’交給 M’執行,也造成了很差的局部性,因為 G’和 G 是相關的,最好放在 M 上執行,而不是其他 M’。
- 系統開銷大
- 系統調用 (CPU 在 M 之間的切換) 導緻頻繁的線程阻塞和取消阻塞操作增加了系統開銷。
M 想要執行、放回 G 都必須通路全局 G 隊列,并且 M 有多個,即多線程通路同一資源需要加鎖進行保證互斥 / 同步,是以全局 G 隊列是有互斥鎖進行保護的。
GMP新版排程器(記憶圖-GMP)
- 解決GM老版排程器的問題
- M(線程):N(協程)關系
- 建立 M 個線程(CPU 執行排程的機關),之後建立的 N 個 goroutine 都會依附在這 M 個線程上執行。
- 在同一時刻,一個線程上隻能跑一個 goroutine。當 goroutine 發生阻塞時,runtime 會把目前 goroutine 排程走,讓其他 goroutine 來執行。
- 任務偷取(work stealing)
- 全局隊列已經沒有 G,那 m 就要執行 work stealing (偷取):從其他有 G 的 P 哪裡偷取一半 G 過來,放到自己的 P 本地隊列
- 讓出執行權(hand off)
- 某個G堵塞,線程釋放綁定的P,把P轉移給其它空閑線程
“go func() 執行過程?”
- go關鍵字建立一個goroutine入隊,如果本地P隊列滿了則入隊全局G隊列
- 從P隊列中隊頭的G交給M執行
- P有兩個關鍵特性
- work stealing
- hand off
檢視更多Github