深入理解 Go 併發,靈活的 goroutine 模型與調度策略

go中,協程co-routine被改爲goroutine,一個goroutine只佔幾 kb,因此可以有大量的goroutine存在,另一方面goroutine 的調度器非常靈活,本文給大家介紹下Go併發的方法之goroutine模型與調度策略,感興趣的朋友一起看看吧

單進程操作系統

早期的單進程操作系統,可以理解爲只有一個時間軸,CPU 順序執行每一個進程 / 線程,這種順序執行的方式,CPU 同一時間智能處理一個指令,一個任務一個任務去處理

這樣就會導致進程阻塞的話,CPU 就會卡在當前進程,一直在等待,CPU 就會浪費

多線程 / 多進程操作系統
CPU 利用輪詢機制,調度各個進程,每個進程都分配固定的時間片,這個時間片很小,先執行進程 A,如果 A 結束了,那沒問題切換到下一進程 B,但如果時間片內 A 沒結束,CPU 不管 A 沒結束,會強制切換到下一進程 B,以此類推,CPU 就避免了阻塞在某一進程

但這樣的問題是,在頻繁切換進程的過程中,進程的切換就必然會導致切換成本,例如保存當前線程狀態,系統調用,環境的上下文切換,各種拷貝複製就會導致時間浪費,大部分時間都用在切換了,進程數量越多,切換的浪費就越大。

因此軟件的目標就是提高 CPU 利用率, 另一方面,進程的內存佔用也是很大的問題

1:N 模型

程序員的任務就是在用戶態調接口,開發業務,內核態負責調硬件,調系統資源,用戶線程和內核線程一一綁定,CPU 只需要管內核線程,這樣的內核線程稱爲thread,用戶線程稱爲co-routinethread通過管理協程調度器,管理多個協程,這樣 CPU 還是隻管理一個線程

這樣就在用戶態實現了併發,CPU 也不用切換了,只用在協程切換時消耗很少的資源,解決了 CPU 高消耗調度的問題。

這樣的弊端是有一個協程阻塞了,下一個協程就不能執行了

M:N 模型
M 個線程通過協程調度器,管理多個協程,CPU 的調度優化我們沒法做,這個協程調度器就非常重要了

goroutine
go中,協程co-routine被改爲goroutine,一個goroutine只佔幾 kb,因此可以有大量的goroutine存在,另一方面goroutine 的調度器非常靈活

goroutine 早期調度器
早期的goroutine調度器有一個全局的goroutine隊列,這個隊列有一個鎖,每個線程要創建、銷燬、運行一個goroutine都要先獲取一個鎖,然後執行goroutine,執行完畢後再把鎖還回去

這樣的問題是激烈的鎖競爭.

另一方面,當一個線程執行一個goroutine時,這個goroutine新創建了一個goroutine,爲了保證併發,這個新goroutine肯定得執行,這個新創建的就被放在下一個線程去執行,這實際上不滿足程序的局部性原理, 另外系統在頻繁調用不同的線程還是會造成系統開銷

GMP
在操作系統中,有一個操作系統調度器,用來調度CPU,來處理不同的內核線程,在這之上每個線程有一個處理器,這個處理器裏有goroutine的各種資源,堆、棧、數據等,這樣的處理器稱爲 P,每個 P 管理一個自己的本地隊列,這個隊列裏存放着這個 P 要處理的goroutine

goroutine要被處理時,線程獲取一個 P,P 調度出一個goroutine交給線程,線程對goroutine進行處理,除了 P 的本地隊列外,還有一個全局隊列,裏面也存放着要運行的goroutine

調度器設計策略

複用線程
work stealing

當一個線程正在執行一個goroutine時,並且它的 P 本地隊列裏還有待執行的goroutine,別的 P 空閒的話就會幫它的線程偷取一個goroutine

hand off

如果說一個 P 正執行的goroutine突然阻塞了,比如在等待輸入之類的,那麼這時候這個 P 的線程,也就是這個 CPU 實際上沒有被利用,這時候就會創建 / 喚醒一個新的線程,這時候讓阻塞的goroutine繼續阻塞。

當前 CPU 變爲睡眠狀態,但把物理CPU切換走到不阻塞的線程上,其餘的 P 和 P 的本地隊列直接被轉移到新的線程上控制,如果之前的阻塞routine又不阻塞了,那把這個goroutine又加入到別的隊列後。

並行

P 的數量可以宏定義,例如爲 CPU 核心數 / 2

搶佔

在 1:1 模型中,一個協程和一個線程綁定,當有別的協程需要運行時,當前的協程除非釋放出這個線程資源,否則新來的就只能等着

而 goroutine 的機制是,每個 goroutine 只有 10ms,時間用完後新的 goroutine 一定會搶佔這個 CPU,沒有誰優先,大家都很平均

全局隊列

一個線程如果沒有要執行的goroutine,根據work stealing,去別的本地隊列偷,如果別的隊列也沒得偷,就去全局隊列裏拿,全局隊列裏別的goroutine前移。

到此這篇關於Go併發的方法之goroutine模型與調度策略的文章就介紹到這了

**文章首發:**https://www.jb51.net/article/229491.htm

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/sDkRMD0vdtmIKrGMkA-4Qw