天天看點

【GO面試精要】GMP并發模型、GoroutineGMP并發模型

GMP并發模型

程序與線程與協程
【GO面試精要】GMP并發模型、GoroutineGMP并發模型

多個線程屬于同一個程序并共享記憶體空間,線程之間的通訊基于共享的記憶體進行。

Go語言的排程器使用與CPU數量相等的線程來排程多個Goroutine。

”為什麼用Go語言?“

程序、線程存在問題:

  1. CPU高消耗
    • 切換線程上下文需要申請、銷毀資源消耗時間高
  2. 記憶體高占用
    • 線程占用1M以上的記憶體空間

協程(Goroutine)的優點:

  1. 占用的記憶體更小(幾kb)
    • 初始為2kb,如果棧空間不足則自動擴容
  2. 排程更靈活(runtime排程)
    • Go自己實作的排程器,建立和銷毀的消耗非常小,是使用者級。
  3. 搶占式排程(10ms)
    • 編譯器插入搶占指令,函數調用時檢查目前Goroutine是否發起搶占請求
  4. 1.14版本後支援基于信号的異步搶占(20ms)
    • 垃圾回收掃描棧時觸發搶占排程
    • 解決搶占式排程因垃圾回收和循環長時間占用資源(無法執行搶占指令)導緻程式暫停

GMP并發模型

GMP

【GO面試精要】GMP并發模型、GoroutineGMP并發模型

圖-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老版排程器:

  1. 激烈的鎖競争
    • 從全局隊列中擷取G,需要加鎖
  2. 局部性差
    • 比如當 G 中包含建立新協程的時候,M 建立了 G’,為了繼續執行 G,需要把 G’交給 M’執行,也造成了很差的局部性,因為 G’和 G 是相關的,最好放在 M 上執行,而不是其他 M’。
  3. 系統開銷大
    • 系統調用 (CPU 在 M 之間的切換) 導緻頻繁的線程阻塞和取消阻塞操作增加了系統開銷。
M 想要執行、放回 G 都必須通路全局 G 隊列,并且 M 有多個,即多線程通路同一資源需要加鎖進行保證互斥 / 同步,是以全局 G 隊列是有互斥鎖進行保護的。
【GO面試精要】GMP并發模型、GoroutineGMP并發模型

GMP新版排程器(記憶圖-GMP)

  1. 解決GM老版排程器的問題
  2. M(線程):N(協程)關系
    • 建立 M 個線程(CPU 執行排程的機關),之後建立的 N 個 goroutine 都會依附在這 M 個線程上執行。
    • 在同一時刻,一個線程上隻能跑一個 goroutine。當 goroutine 發生阻塞時,runtime 會把目前 goroutine 排程走,讓其他 goroutine 來執行。
  3. 任務偷取(work stealing)
    • 全局隊列已經沒有 G,那 m 就要執行 work stealing (偷取):從其他有 G 的 P 哪裡偷取一半 G 過來,放到自己的 P 本地隊列
  4. 讓出執行權(hand off)
    • 某個G堵塞,線程釋放綁定的P,把P轉移給其它空閑線程

“go func() 執行過程?”

【GO面試精要】GMP并發模型、GoroutineGMP并發模型
  1. go關鍵字建立一個goroutine入隊,如果本地P隊列滿了則入隊全局G隊列
  2. 從P隊列中隊頭的G交給M執行
  3. P有兩個關鍵特性
    1. work stealing
    2. hand off

檢視更多Github

繼續閱讀