GO調度器模型
GO的調度器可以充分利用多核心CPU,任何時候都有M個go協程在N個系統線程上進行調度, 這些線程在最多 GOMAXPROCS 個CPU核心上運行,這種調度模型稱之為GMP模型:
- G : 協程(goroutine)
- M : 系統線程(machine)
- P : 處理器(processor)
如下圖,每個P都有一個本地隊列存放待運行的G,另外有一個全局隊列,每個M需要依附在P上運行,一個P可以對應多個M, 但是同一時間一個P上只會有一個正在運行的M。
每輪調度只需要找一個可運行的G執行它就行,這個查找過程如下:
runtime.schedule() {
// 1/61 的概率去全局隊列找一個G來運行
// 如果沒有找到,到本地隊列中找
// 如果沒有找到,
// 嘗試從其他的P中偷取G來運行
// 如果沒有,檢查全局隊列
// 如果沒有,檢查 Net Poller
}
GO協程的狀態
GO協程和線程一樣有三種狀態,一個協程可以處于下面三種狀態中的一種: Waiting , Runnable 和 Executing 。
- Waiting : 這種狀態意味著協程被暫停運行需要等待某些事情完成才能繼續運行。 比如等待一個系統調用的返回或者一個同步調用(原子或者鎖操作)。這種類型的延時是性能低下的主要原因。
- Runnable : 這種狀態說明協程正在等待被運行。如果有很多協程都在等待運行,那么協程需要等待一段更長的時間, 而且每個協程被分配的運行時間也會減少。這種調度延時也是一種性能低下的原因。
- Executing : 這種狀態說明此協程被放在了M中正在執行它的指令。
調度時機
GO程序中的下列4類事件可以觸發調度器執行調度任務。并不是說這些事件發生時調度器一定會執行調度,只是說此時調度器有機會執行調度:
- 使用 go 關鍵字的地方, go 關鍵字用于創建協程。在一個新的協程被創建的時候,就會給調度器一個機會執行調度任務。
- 垃圾回收,GC是在自己的一套協程中運行,所以在GC過程中需要被調度執行,在GC過程中調度器會優先調度需要接觸堆內存的協程
- 系統調用,在系統調用時會導致協程阻塞這個M,調度器會將此協程調度出去或者使用一個新的M來執行隊列中的其他協程。
- 同步和編排, atomic,mutex和channel操作都可能會阻塞這個協程,此時調度器可以調度一個新的協程來運行。
異步調用
大多數操作系統都支持網絡輪詢,例如MacOS的kqueue,linux的epoll接口。Go會利用網絡輪詢接口來異步處理網絡請求, 當G調用網絡系統調用時調度器會將此G調度出去以避免M被阻塞,然后調度隊列中的其他G繼續執行,因此不需要創建一個新的M,減少調度開銷。
在圖1中,Goroutine-1正在M上運行,此時本地隊列中有3個G在等待運行,網絡輪詢器上是空的。
圖2中,Goroutine-1希望進行網絡系統調用,此時將Goroutine-1移至網絡輪詢器上處理異步網絡系統調用然后將Goroutine-2調度到M上繼續運行。
圖3中,網絡調用完成,此時Goroutine-1被放回本地隊列中,當調度到Goroutine-1時,它可以繼續執行接下來的指令,這里最大的好處是執行網絡系統調用不需要額外的M, 網絡輪詢器實際是一個系統線程專門用于處理異步網絡請求。
同步調用
當G調用同步系統調用時會怎樣?例如文件相關的系統調用以及使用CGO時調用C函數也是同步調用,此時M會被此G阻塞。
圖4中,Goroutine-1正在M1上運行,他要執行同步系統調用,此時會阻塞M1。
圖5中,調度器可以探測出M1被Goroutine-1阻塞了,此時調度器會將M1和P分開,但是Goroutine-1還是在M1上。 然后搞一個M2繼續在P上運行,此時可以調度Goroutine-2繼續執行。GO會維護一個線程池只有線程池中沒有M才會創建新的M,所以這種M切換是非常快的。
圖6中,Goroutine-1的同步阻塞調用完成,此時Goroutine-1會被轉移到P的本地隊列中待被調度執行,此時M1會被放在線程池待下次使用。
任務竊取
任務竊取作用是平衡P之間的負載,如果某個P上的G都執行完了,此時會檢查其他P上有沒有可執行的G,如果有則會竊取其他P上的G來執行。
圖7中,有兩個P,每個P上有4個G,全局隊列中也有一個G。
圖8中,P1上的G全部執行完了,但是P2和全局隊列上還有G待執行。此時P1需要竊取其他G來執行,竊取規則和調度規則是一樣的參考上面的 runtime.schedule 。
圖9中,根據竊取規則,P1會將P2上一半的G竊取過來執行。
圖10中,如果此時P2上的G都執行完了,并且P1的本地隊列中也沒有G了會怎么辦?
圖11中,P2上的G都執行完,它要開始竊取任務,但是P1上也沒有G了,根據竊取規則他會把全局隊列上的G拿過來執行。
參考
這篇文章基本上是翻譯下面的文章,然后加了一些自己的理解。