goroutine 上下文切換

pardon110發表於2019-10-12

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 只會使用一個執行緒,所以只能利用單核

go狀態協程

相關文章