go 基於執行緒的基礎上,在使用者態設計使用者態執行緒模型與排程器,減少了併發排程代價。但也帶來goroutine非常規線性順序執行的困惑,而這顯然
runtime
排程 息息相關,本文作為總結,會持續更新。
runtime
在go中對系統API的排程,都會被 runtime
層攔截以便達到高效排程
使用者態協程排程機制
- N:1 多個使用者態協程執行在一個OS執行緒上
- 1:1 一個使用者態協程對應一個OS執行緒
- M:N 任意數量的使用者態協程可以執行在任意數量的OS執行緒上
goroutine 模型
M 系統執行緒
G Goroutine golang協程
P Context M與P的中介,實現M:N模型的關鍵
M必須拿到P才能夠對G進行排程
排程點
golang採用的是第三種排程機制, 顯然執行緒較多時,完全依賴系統排程開銷大
Goroutine 在 system call 和 channel call 時都可能發生阻塞,當程式發生 system call,M 會發生阻塞,同時喚起(或建立)一個新的 M 繼續執行其他的 G
當程式發起一個 channel call,程式可能會阻塞,但不會阻塞 M,G 的狀態會設定為 waiting,M 繼續執行其他的 G,當 G 的呼叫完成,會有一個可用的 M 繼續執行它
goroutine上下文切換
上面的都是官話,一切操作都只是為了壓榨CPU,誰讓它那麼快,又出現多核。
- 出現阻塞,意味CPU在當前執行域沒活幹了,它在乾等,換go而言,排程器要goroutine上下文切換
chan 讀寫出現阻塞時,runtime 會隱式地進行上下文切換,而在極個別情況下,需要程式設計師顯式編碼操作,如下所示
至於切換到哪個goroutine,由排程器決定。但可以肯定的是對同一chan所相關的goroutine執行有序
// 顯式交給排程器切換,否則有的goroutine就餓死了
runtime.Gosched()
Goroutine vs Python yield
程式語言都是基於作業系統,顯然goroutine能做的,其它 語言也可以。
只不過,比之於java,python之類上古語言,go做的更多而已。
cmp | goroutine | python yield |
---|---|---|
成本 | Go 原生支援協程,通過 go func() 就可以建立一個 goroutine | Python 可以通過 gevent.spawn 來新建一個 coroutine,需要第三方庫來支援 |
切換 | Goroutine 之間的通訊更簡單,通過 channel call 即可實現,上下文切換透明 | Python 需要 yield 來傳遞資料和切換上下文(通過一些庫封裝後對呼叫者來說也是透明的,比如:gevent/tornado) |
核數 | Goroutine 可以被多個執行緒排程,可以利用多核 | Python coroutine 只會使用一個執行緒,所以只能利用單核 |