Go並發的方法之goroutine模型與調度策略

學習劉丹冰《8小時轉職golang工程師》,本節都是原理

單進程操作系統

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

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

多線程/多進程操作系統

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

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

因此軟件的目標就是提高CPU利用率

另一方面,進程的內存占用也是很大的問題

1:N模型

程序員的任務就是在用戶態調接口,開發業務,內核態負責調硬件,調系統資源,用戶線程和內核線程一一綁定,CPU隻需要管內核線程,這樣的內核線程稱為thread,用戶線程稱為co-routine,thread通過管理協程調度器,管理多個協程,這樣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模型與調度策略的文章就介紹到這瞭,更多相關Go goroutine模型與調度策略內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: