天天看點

Go語言的GPM排程器是什麼?

雲栖号資訊:【 點選檢視更多行業資訊

在這裡您可以找到不同行業的第一手的上雲資訊,還在等什麼,快來!

相信很多人都聽說過Go語言天然支援高并發,原因是内部有協程(goroutine)加持,可以在一個程序中啟動成千上萬個協程。那麼,它憑什麼做到如此高的并發呢?那就需要先了解什麼是并發模型。

并發模型

著名的C++專家Herb Sutter曾經說過“免費的午餐已經終結”。為了讓代碼運作的更快,單純依靠更快的硬體已經無法得到滿足,我們需要利用多核來挖掘并行的價值,而并發模型的目的就是來告訴你不同執行實體之間是如何協作的。

當然,不同的并發模型的協作方式也不盡相同,常見的并發模型有七種:

  • 線程與鎖
  • 函數式程式設計
  • Clojure之道
  • actor
  • 通訊順序程序(CSP)
  • 資料級并行
  • Lambda架構

而今天,我們隻講與Go語言相關的并發模型CSP。

CSP篇

CSP,全稱Communicating Sequential Processes,意為通訊順序程序,它是七大并發模型中的一種,它的核心觀念是将兩個并發執行的實體通過通道channel連接配接起來,所有的消息都通過channel傳輸。其實CSP概念早在1978年就被東尼·霍爾提出,由于近來Go語言的興起,CSP又火了起來。

那麼CSP與Go語言有什麼關系呢?接下來我們來看Go語言對CSP并發模型的實作——GPM排程模型。

GPM排程模型

GPM代表了三個角色,分别是Goroutine、Processor、Machine。

Go語言的GPM排程器是什麼?
  • Goroutine:就是咱們常用的用go關鍵字建立的執行體,它對應一個結構體g,結構體裡儲存了goroutine的堆棧資訊
  • Machine:表示作業系統的線程
  • Processor:表示處理器,有了它才能建立G、M的聯系

Goroutine

Goroutine就是代碼中使用go關鍵詞建立的執行單元,也是大家熟知的有“輕量級線程”之稱的協程,協程是不為作業系統所知的,它由程式設計語言層面實作,上下文切換不需要經過核心态,再加上協程占用的記憶體空間極小,是以有着非常大的發展潛力。

在Go語言中,Goroutine由一個名為runtime.go的結構體表示,該結構體非常複雜,有40多個成員變量,主要存儲執行棧、狀态、目前占用的線程、排程相關的資料。還有玩大家很想擷取的goroutine辨別,但是很抱歉,官方考慮到Go語言的發展,設定成私有了,不給你調用😏。

stack struct {
        lo uintptr
        hi uintptr
    }                             // 棧記憶體:[stack.lo, stack.hi)
    stackguard0    uintptr
    stackguard1 uintptr

    _panic       *_panic
    _defer       *_defer
    m            *m                // 目前的 m
    sched        gobuf
    stktopsp     uintptr        // 期望 sp 位于棧頂,用于回溯檢查
    param        unsafe.Pointer // wakeup 喚醒時候傳遞的參數
    atomicstatus uint32
    goid         int64
    preempt      bool           // 搶占信号,stackguard0 = stackpreempt 的副本
    timer        *timer         // 為 time.Sleep 緩存的計時器

    ...
}           

Goroutine排程相關的資料存儲在sched,在協程切換、恢複上下文的時候用到。

sp   uintptr
    pc   uintptr
    g    guintptr
    ret  sys.Uintreg
    ...
}           

Machine

M就是對應作業系統的線程,最多會有GOMAXPROCS個活躍線程能夠正常運作,預設情況下GOMAXPROCS被設定為核心數,假如有四個核心,那麼預設就建立四個線程,每一個線程對應一個runtime.m結構體。線程數等于CPU個數的原因是,每個線程配置設定到一個CPU上就不至于出現線程的上下文切換,可以保證系統開銷降到最低。

Go語言的GPM排程器是什麼?

M裡面存了兩個比較重要的東西,一個是g0,一個是curg。

g0:會深度參與運作時的排程過程,比如goroutine的建立、記憶體配置設定等

curg:代表目前正線上程上執行的goroutine。

剛才說P是負責M與G的關聯,是以M裡面還要存儲與P相關的資料。

Go語言的GPM排程器是什麼?
  • p:正在運作代碼的處理器
  • nextp:暫存的處理器
  • old:系統調用之前的線程的處理器

Processor

Proccessor負責Machine與Goroutine的連接配接,它能提供線程需要的上下文環境,也能配置設定G到它應該去的線程上執行,有了它,每個G都能得到合理的調用,每個線程都不再渾水摸魚,真是居家必備之良品。

同樣的,處理器的數量也是預設按照GOMAXPROCS來設定的,與線程的數量一一對應。

sp   uintptr
    pc   uintptr
    g    guintptr
    ret  sys.Uintreg
    ...
}           

結構體P中存儲了性能追蹤、垃圾回收、計時器等相關的字段外,還存儲了處理器的待運作隊列,隊列中存儲的是待執行的Goroutine清單。

三者的關系

首先,預設啟動四個線程四個處理器,然後互相綁定。

Go語言的GPM排程器是什麼?

這個時候,一個Goroutine結構體被建立,在進行函數體位址、參數起始位址、參數長度等資訊以及排程相關屬性更新之後,它就要進到一個處理器的隊列等待發車。

Go語言的GPM排程器是什麼?

啥,又建立了一個G?那就輪流往其他P裡面放呗,相信你排隊取号的時候看到其他視窗沒人排隊也會過去的。

Go語言的GPM排程器是什麼?

假如有很多G,都塞滿了怎麼辦呢?那就不把G塞到處理器的私有隊列裡了,而是把它塞到全局隊列裡(候車大廳)。

Go語言的GPM排程器是什麼?

除了往裡塞之外,M這邊還要瘋狂往外取,首先去處理器的私有隊列裡取G執行,如果取完的話就去全局隊列取,如果全局隊列裡也沒有的話,就去其他處理器隊列裡偷,哇,這麼饑渴,簡直是惡魔啊!

Go語言的GPM排程器是什麼?

如果哪裡都沒找到要執行的G呢?那M就會因為太失望和P斷開關系,然後去睡覺(idle)了。

Go語言的GPM排程器是什麼?

那如果兩個Goroutine正在通過channel做一些恩恩愛愛的事阻塞住了怎麼辦,難道M要等他們完事了再繼續執行?顯然不會,M并不稀罕這對Go男女,而會轉身去找别的G執行。

Go語言的GPM排程器是什麼?

系統調用

如果G進行了系統調用syscall,M也會跟着進入系統調用狀态,那麼這個P留在這裡就浪費了,怎麼辦呢?這點精妙之處在于,P不會傻傻的等待G和M系統調用完成,而會去找其他比較閑的M執行其他的G。

Go語言的GPM排程器是什麼?

當G完成了系統調用,因為要繼續往下執行,是以必須要再找一個空閑的處理器發車。

Go語言的GPM排程器是什麼?

如果沒有空閑的處理器了,那就隻能把G放回全局隊列當中等待配置設定。

Go語言的GPM排程器是什麼?

sysmon

sysmon是我們的保潔阿姨,它是一個M,又叫監控線程,不需要P就可以獨立運作,每20us~10ms會被喚醒一次出來打掃衛生,主要工作就是回收垃圾、回收長時間系統排程阻塞的P、向長時間運作的G發出搶占排程等等。

詞條解釋

東尼·霍爾

東尼·霍爾,英國計算機科學家,圖靈獎得主,他設計了牛氣沖天的快速排序算法、霍爾邏輯以及CSP模型。2011年獲頒約翰·馮諾依曼獎。

Go語言的GPM排程器是什麼?

【雲栖号線上課堂】每天都有産品技術專家分享!

課程位址:

https://yqh.aliyun.com/live

立即加入社群,與專家面對面,及時了解課程最新動态!

【雲栖号線上課堂 社群】

https://c.tb.cn/F3.Z8gvnK

原文釋出時間:2020-04-17

本文作者:平也

本文來自:“

掘金

”,了解相關資訊可以關注“掘金”