解決Golang中goroutine執行速度的問題
突然想到瞭之前一直沒留意的for循環中開goroutine的執行順序問題,就找瞭段代碼試瞭試,試瞭幾次後發現幾個有意思的地方,我暫時沒有精力往更深處挖掘,希望有golang大神能簡單說一說這幾個地方是怎麼回事。
代碼:
package main import "fmt" func Count(ch chan int) { fmt.Println("Count doing") ch <- 1 fmt.Println("Counting") } func main() { chs := make([]chan int, 100) for i := 0; i < 100; i++ { chs[i] = make(chan int) go Count(chs[i]) fmt.Println("Count",i) } for i, ch := range chs { <-ch fmt.Println("Counting ", i) } }
試瞭幾次之後,反復的想goroutine執行的問題。
根據下面的輸出,我能看到的是:
1. for循環的速度 比 for中開出goroutine並執行的速度 執行的快
2. 但是 開goroutine和執行第一個fmt的速度可能趕上 for循環的速度 比如前12個count和count doing
3. 關鍵問題,第二個for循環執行的fmt竟然要比goroutine中的第二個fmt快??(放入channel很耗時?)
4. main結束時,也就是第二個for循環結束時, 還有goroutine中的第二個fmt沒執行
輸出:
Count 0 Count 1 Count 2 Count 3 Count 4 Count 5 Count 6 Count 7 Count 8 Count 9 Count 10 Count 11 Count doing Count doing Count doing Count doing Count doing Count 12 Count doing Count doing Count doing Count doing Count doing Count doing Count doing Count 13 Count 14 Count 15 Count 16 Count 17 Count 18 Count 19 Count 20 Count 21 Count doing Count doing Count doing Count 22 Count doing Count doing Count doing Count 23 Count 24 Count 25 Count 26 Count 27 Count 28 Count 29 Count 30 Count doing Count 31 Count doing Count doing Count 32 Count 33 Count 34 Count 35 Count doing Count 36 Count doing Count doing Count 37 Count 38 Count doing Count doing Count doing Count doing Count 39 Count 40 Count 41 Count 42 Count 43 Count doing Count doing Count 44 Count 45 Count 46 Count 47 Count doing Count 48 Count 49 Count doing Count doing Count 50 Count 51 Count doing Count doing Count doing Count doing Count doing Count 52 Count 53 Count doing Count doing Count doing Count doing Count 54 Count doing Count 55 Count 56 Count 57 Count 58 Count 59 Count 60 Count 61 Count 62 Count 63 Count 64 Count 65 Count doing Count doing Count doing Count 66 Count 67 Count 68 Count 69 Count doing Count 70 Count doing Count 71 Count 72 Count doing Count 73 Count doing Count doing Count 74 Count doing Count 75 Count 76 Count 77 Count doing Count doing Count doing Count doing Count 78 Count 79 Count 80 Count 81 Count 82 Count 83 Count 84 Count 85 Count 86 Count 87 Count 88 Count 89 Count 90 Count 91 Count 92 Count 93 Count 94 Count doing Count doing Count doing Count doing Count doing Count doing Count doing Count doing Count 95 Count doing Count 96 Count doing Count 97 Count 98 Count doing Count 99 Count doing Count doing Counting 0 Counting 1 Counting 2 Counting 3 Counting 4 Counting 5 Counting 6 Count doing Count doing Counting 7 Counting 8 Count doing Counting Count doing Counting 9 Counting Count doing Count doing Count doing Count doing Count doing Counting Count doing Count doing Count doing Counting Count doing Counting Count doing Counting 10 Counting 11 Counting Count doing Count doing Count doing Count doing Count doing Count doing Counting Count doing Count doing Counting Counting Count doing Count doing Count doing Count doing Counting Count doing Counting Count doing Count doing Counting 12 Counting 13 Counting 14 Counting 15 Counting 16 Counting 17 Counting 18 Counting 19 Counting 20 Counting 21 Counting 22 Counting 23 Counting 24 Counting 25 Counting 26 Counting 27 Counting 28 Counting 29 Counting 30 Counting 31 Counting 32 Counting 33 Counting 34 Counting 35 Counting 36 Counting 37 Counting 38 Counting 39 Counting 40 Counting 41 Counting 42 Counting 43 Counting 44 Counting 45 Counting 46 Counting 47 Counting 48 Counting 49 Counting 50 Counting 51 Counting 52 Counting 53 Counting 54 Counting 55 Counting 56 Counting Counting Counting Counting Counting Counting Count doing Counting Count doing Counting Counting Counting 57 Counting 58 Counting 59 Counting 60 Counting 61 Counting 62 Counting 63 Counting 64 Counting 65 Counting 66 Counting 67 Counting 68 Counting 69 Counting 70 Counting 71 Counting 72 Counting 73 Counting 74 Counting 75 Counting 76 Counting Counting Counting Counting Counting Counting Counting Counting Counting Counting Counting Counting Counting Counting Counting Counting Counting Counting Counting Counting 77 Counting 78 Counting 79 Counting 80 Counting 81 Counting 82 Counting 83 Counting 84 Counting 85 Counting 86 Counting 87 Counting 88 Counting 89 Counting 90 Counting 91 Counting 92 Counting 93 Counting 94 Counting 95 Counting 96 Counting 97 Counting 98 Counting 99
補充:【golang】goroutine調度的坑
今天說說我遇到的一個小坑, 關於goroutine 調度的問題。
關於goroutine的調度,網上資料已經一大堆瞭,這裡就不再贅述瞭。
還是簡單的說一下我理解的goroutine的調度。goroutine是語言層面的,它和內核線程是M:N的關系,並且用瞭分段棧,是相當輕量瞭。
如果設置runtime.GOMAXPROCS為1,那麼會有一個上下文G,在G上會有一個對應的內核線程M,內核線程M上可以對應很多個goroutine記作G,每個上下文都會有一個隊列稱作runqueue,在用go關鍵字開啟一個goroutine的時候,該goroutine就會被裝入runqueue中,然後被M用來執行,如果剛好有兩個goroutine在隊列裡,先執行的goroutine因為執行一些耗時操作(系統調用,讀寫 channel,gosched 主動放棄,網絡IO)會被掛起(扔到全局runqueue),然後調度後面的goroutine。
好,重點在這裡,看一下下面的一段代碼
func main(){ runtime.GOMAXPROCS(1) waitGroup.Add(1) go func(){ defer waitGroup.Done() for i := 0;i < 20;i++ { fmt.Println("hello") f, _ := os.Open("./data") f.Write([]byte("hello")) } }() waitGroup.Add(1) go func(){ defer waitGroup.Done() for { } }() waitGroup.Wait() }
這段代碼你運行,你會發現,永遠都會被阻塞住,hello永遠都打印不出來
好,這裡出現瞭兩個問題
1.為什麼死循環的goroutine總是先運行?按理說不應該是隨機的嗎?
2.為什麼死循環的goroutine會阻塞而沒有被掛起?
先看第二個問題。這裡的話,我當時也很苦惱,於是在網上發瞭問題,得到的回復是,死循環不屬於上述任何一種需要被掛起的狀態,於是死循環的goroutine會一直運行,想象一個高並發的場景,如果其中一個goroutine因為某種原因陷入死循環瞭,當前執行這個goroutine的OS thread基本就會被一直執行這個goroutine,直到程序結束,這簡直是一場災難。但是,1.12 會修正這個小問題。我們還是默默的等待新版本發佈吧。
再看第一個問題。為什麼死循環的goroutine總是先運行?按理說不應該是隨機的嗎?測試過很多次,都是第二個goroutine先運行。嗯,其實就算是第二個goroutine先運行也是具有隨機性的,這關於golang的編譯器如何去實現隨機。看一下大佬的回答 :
<不是說測試很多遍它就會一直這樣,語言規范沒有說必須是這個順序,那編譯器怎麼實現都可以,因為都不違反規范。
所以你要把它看作是隨機的,不能依賴這種未確定的行為,不然很可能新版的編譯器就會破壞你依賴的事實。有些項目不敢升級編譯器版本,就是因為依賴瞭特定版本的編譯器的行為,一升級就壞瞭。
不是你自己測試很多遍你就能依賴它,編譯器、操作系統、硬件等等不同,都有可能出現不同的結果。可以依賴的隻有語言規范( https://golang.org/ref/spec ),編譯器實現者是一定會遵守的。
到這裡也算是解決瞭上述的兩個問題瞭。
來看一下另外一個版本
func main(){ runtime.GOMAXPROCS(1) waitGroup.Add(1) go func(){ defer waitGroup.Done() for { } }() waitGroup.Add(1) go func(){ defer waitGroup.Done() for i := 0;i < 20;i++ { fmt.Println("hello") f, _ := os.Open("./data") f.Write([]byte("hello")) http.Get("http://www.baidu.com") fmt.Println("request successful") } }() waitGroup.Wait() }
執行結果是,會先打印一個hello,然後陷入死循環,這也是說明瞭goroutine在遇到耗時操作或者系統調用的時候,後面的代碼都不會執行瞭(request successful 沒有被打印),會被拋到全局runqueue裡去,然後執行runqueue中等待的goroutine
希望能夠幫助和我一樣正在學習golang的友軍們更好的理解goroutine的調度問題
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。如有錯誤或未考慮完全的地方,望不吝賜教。
推薦閱讀:
- 示例剖析golang中的CSP並發模型
- Go語言如何輕松編寫高效可靠的並發程序
- Go語言CSP並發模型實現MPG
- Go語言中的並發goroutine底層原理
- golang基礎之waitgroup用法以及使用要點