本文記錄了本人對Golang調度器的理解和跟蹤調度器的方法,特別是一個容易忽略的goroutine執行順序問題,看了不少篇Golang調度器的文章都沒提到這個點,分享出來一塊兒學習,歡迎交流指正。git
爲了方便剛接觸操做系統和高級語言的同窗,先用大白話介紹下什麼是調度器。 調度,是將多個程序合理的安排到有限的CPU上來使得每一個程序都可以得以執行,實現宏觀的併發執行。好比咱們的電腦CPU只有四核甚至雙核,但是咱們卻能夠在電腦上同時運行幾十個程序,這就是操做系統調度器的功勞。但操做系統調度的是進程和線程,線程簡單地說就是輕量級的進程,可是每一個線程仍須要MB級別的內存,並且若是兩個切換的線程在不一樣的進程中,還須要進程切換,會使CPU在調度這件事上花費大量時間。 爲了更合理的利用CPU,Golang經過goroutine原生支持高併發,goroutine是由go調度器在語言層面進行調度,將goroutine安排到線程上,能夠更充分地利用CPU。github
Golang的調度器在runtime中實現,咱們每一個運行的程序執行前都會運行一個runtime負責調度goroutine,咱們寫的代碼入口要在main包下的main函數中也是由於runtime.main函數會調用main.main。Golang的調度器在2012被重寫過一次,如今使用的是新版的G-P-M調度器,可是咱們仍是先來看下老的G-M調度器,這樣才能夠更好的體會當前調度器的強大之處。golang
下面是舊調度器的G-P模型: shell
M:表明線程,goroutine都是由線程來執行的; Global G Queue:全局goroutine隊列,其中G就表明goroutine,全部M都從這個隊列中取出goroutine來執行。 這種模型比較簡單,可是問題也很明顯:後來Go語言開發者改善了調度器爲G-P-M模型,以下圖: markdown
其中G仍是表明goroutine,M表明線程,全局隊列依然存在;而新增長的P表明邏輯processor,如今G的眼中只有P,在G的眼裏P就是它的CPU。而且給每一個P新增長了局部隊列來保存本P要處理的goroutine。 這個模型的調度方法以下:是否是感受本身對Go調度器工做原理已經有個初步的瞭解了?下面指出一個坑給你踩一下,當心了! 請看下面這段代碼輸出什麼:併發
func main() { done := make(chan bool) values := []string{"a", "b", "c"} for _, v := range values { fmt.Println("--->", v) go func(u string) { fmt.Println(u) done <- true }(v) } // wait for all goroutines to complete before exiting for _ = range values { <-done } } 複製代碼
先仔細想一下再看答案哦!函數
實際的數據結果是:高併發
---> a
---> b
---> c
c
b
a
複製代碼
Go調度器示例代碼能夠在跟着示例代碼學golang中查看,持續更新中,想系統學習Golang的同窗能夠關注一下。oop
可能你的第一反應是「不該該是輸出a,b,c,嗎?爲何輸出是c,a,b呢?」 這裏咱們雖然是使用for循環建立了3個goroutine,並且建立順序是a,b,c,按以前的分析應該是將a,b,c三個goroutine依次放進P的局部隊列,而後按照順序依次執行a,b,c所在的goroutine,爲何每次都是先執行c所在的goroutine呢?這是由於同一邏輯處理器中三個任務被建立後 理論上會按順序 被放在同一個任務隊列,但實際上最後那個任務會被放在專注的next(下一個要被執行的任務的意思)的位置,因此優先級最高,最可能先被執行,因此表現爲在同一個goroutine中建立的多個任務中最後建立那個任務最可能先被執行。學習
這段解釋來自參考文章《Goroutine執行順序討論》中。
GODEBUG這個Go運行時環境變量非常強大,經過給其傳入不一樣的key1=value1,key2=value2… 組合,Go的runtime會輸出不一樣的調試信息,好比在這裏咱們給GODEBUG傳入了」schedtrace=1000″,其含義就是每1000ms,打印輸出一次goroutine scheduler的狀態。 下面演示使用Golang強大的GODEBUG環境變量能夠查看當前程序中Go調度器的狀態:
環境爲Windows10的Linux子系統(WSL),WSL搭建和使用的代碼在learn-golang項目有整理,代碼在文末參考的鳥窩的文章中也能夠找到。
func main() { var wg sync.WaitGroup wg.Add(10) for i := 0; i < 10; i++ { go work(&wg) } wg.Wait() // Wait to see the global run queue deplete. time.Sleep(3 * time.Second) } func work(wg *sync.WaitGroup) { time.Sleep(time.Second) var counter int for i := 0; i < 1e10; i++ { counter++ } wg.Done() } 複製代碼
編譯指令:
go build 01_GODEBUG-schedtrace.go
GODEBUG=schedtrace=1000 ./01_GODEBUG-schedtrace
複製代碼
結果:
SCHED 0ms: gomaxprocs=4 idleprocs=1 threads=5 spinningthreads=1 idlethreads=0 runqueue=0 [4 0 4 0]
SCHED 1000ms: gomaxprocs=4 idleprocs=4 threads=8 spinningthreads=0 idlethreads=3 runqueue=0 [0 0 0 0]
SCHED 2007ms: gomaxprocs=4 idleprocs=0 threads=8 spinningthreads=0 idlethreads=3 runqueue=0 [0 0 0 6]
SCHED 3025ms: gomaxprocs=4 idleprocs=0 threads=8 spinningthreads=0 idlethreads=3 runqueue=0 [0 0 0 6]
SCHED 4033ms: gomaxprocs=4 idleprocs=0 threads=8 spinningthreads=0 idlethreads=3 runqueue=0 [0 0 0 6]
SCHED 5048ms: gomaxprocs=4 idleprocs=0 threads=8 spinningthreads=0 idlethreads=3 runqueue=0 [0 0 0 6]
SCHED 6079ms: gomaxprocs=4 idleprocs=0 threads=8 spinningthreads=0 idlethreads=3 runqueue=0 [0 0 0 6]
SCHED 7081ms: gomaxprocs=4 idleprocs=0 threads=8 spinningthreads=0 idlethreads=3 runqueue=0 [0 0 0 6]
SCHED 8092ms: gomaxprocs=4 idleprocs=0 threads=8 spinningthreads=0 idlethreads=3 runqueue=0 [0 0 0 6]
SCHED 9113ms: gomaxprocs=4 idleprocs=0 threads=8 spinningthreads=0 idlethreads=3 runqueue=0 [0 1 0 1]
SCHED 10129ms: gomaxprocs=4 idleprocs=0 threads=8 spinningthreads=0 idlethreads=3 runqueue=0 [0 1 0 1]
SCHED 11134ms: gomaxprocs=4 idleprocs=0 threads=8 spinningthreads=0 idlethreads=3 runqueue=0 [0 1 0 1]
SCHED 12157ms: gomaxprocs=4 idleprocs=0 threads=8 spinningthreads=0 idlethreads=3 runqueue=0 [0 1 0 1]
SCHED 13170ms: gomaxprocs=4 idleprocs=0 threads=8 spinningthreads=0 idlethreads=3 runqueue=0 [0 1 0 1]
SCHED 14183ms: gomaxprocs=4 idleprocs=0 threads=8 spinningthreads=0 idlethreads=3 runqueue=0 [0 1 0 1]
SCHED 15187ms: gomaxprocs=4 idleprocs=0 threads=8 spinningthreads=0 idlethreads=3 runqueue=0 [0 1 0 1]
SCHED 16187ms: gomaxprocs=4 idleprocs=2 threads=8 spinningthreads=0 idlethreads=5 runqueue=0 [0 0 0 0]
SCHED 17190ms: gomaxprocs=4 idleprocs=2 threads=8 spinningthreads=0 idlethreads=5 runqueue=0 [0 0 0 0]
SCHED 18193ms: gomaxprocs=4 idleprocs=2 threads=8 spinningthreads=0 idlethreads=5 runqueue=0 [0 0 0 0]
SCHED 19196ms: gomaxprocs=4 idleprocs=2 threads=8 spinningthreads=0 idlethreads=5 runqueue=0 [0 0 0 0]
SCHED 20200ms: gomaxprocs=4 idleprocs=4 threads=8 spinningthreads=0 idlethreads=6 runqueue=0 [0 0 0 0]
SCHED 21210ms: gomaxprocs=4 idleprocs=4 threads=8 spinningthreads=0 idlethreads=6 runqueue=0 [0 0 0 0]
SCHED 22219ms: gomaxprocs=4 idleprocs=4 threads=8 spinningthreads=0 idlethreads=6 runqueue=0 [0 0 0 0]
複製代碼
看到怎麼多輸出不要慌, 瞭解每一個字段的含義就很清晰了:
上面的輸出信息已經足夠咱們瞭解咱們的程序運行情況,要想看每一個goroutine、m和p的詳細調度信息,能夠在GODEBUG時加入,scheddetail
:
GODEBUG=schedtrace=1000,scheddetail=1 ./01_GODEBUG-schedtrace
複製代碼
結果以下:
SCHED 0ms: gomaxprocs=4 idleprocs=4 threads=7 spinningthreads=0 idlethreads=2 runqueue=0 gcwaiting=0 nmidlelocked=0 stopwait=0 sysmonwait=0
P0: status=0 schedtick=7 syscalltick=1 m=-1 runqsize=0 gfreecnt=0
P1: status=0 schedtick=2 syscalltick=1 m=-1 runqsize=0 gfreecnt=0
P2: status=0 schedtick=1 syscalltick=1 m=-1 runqsize=0 gfreecnt=0
P3: status=0 schedtick=1 syscalltick=1 m=-1 runqsize=0 gfreecnt=0
M6: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M5: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M4: p=-1 curg=33 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M3: p=-1 curg=49 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M2: p=-1 curg=17 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
M1: p=-1 curg=-1 mallocing=0 throwing=0 preemptoff= locks=1 dying=0 spinning=false blocked=false lockedg=-1
M0: p=-1 curg=14 mallocing=0 throwing=0 preemptoff= locks=0 dying=0 spinning=false blocked=true lockedg=-1
G1: status=4(semacquire) m=-1 lockedm=-1
G2: status=4(force gc (idle)) m=-1 lockedm=-1
G3: status=4(GC sweep wait) m=-1 lockedm=-1
G4: status=4(sleep) m=-1 lockedm=-1
G5: status=4(sleep) m=-1 lockedm=-1
G6: status=4(sleep) m=-1 lockedm=-1
G7: status=4(sleep) m=-1 lockedm=-1
G8: status=4(sleep) m=-1 lockedm=-1
G9: status=4(sleep) m=-1 lockedm=-1
G10: status=4(sleep) m=-1 lockedm=-1
G11: status=4(sleep) m=-1 lockedm=-1
G12: status=4(sleep) m=-1 lockedm=-1
G13: status=4(sleep) m=-1 lockedm=-1
G14: status=3() m=0 lockedm=-1
G33: status=3() m=4 lockedm=-1
G17: status=3() m=2 lockedm=-1
G49: status=3() m=3 lockedm=-1
複製代碼
代碼能夠在跟着示例代碼學golang中查看,持續更新中,想系統學習Golang的同窗能夠關注一下。
參考資料: