突然想到了之前一直沒(méi)留意的for循環(huán)中開(kāi)goroutine的執(zhí)行順序問(wèn)題,就找了段代碼試了試,試了幾次后發(fā)現(xiàn)幾個(gè)有意思的地方,我暫時(shí)沒(méi)有精力往更深處挖掘,希望有g(shù)olang大神能簡(jiǎn)單說(shuō)一說(shuō)這幾個(gè)地方是怎么回事。
代碼:
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)
}
}
試了幾次之后,反復(fù)的想goroutine執(zhí)行的問(wèn)題。
根據(jù)下面的輸出,我能看到的是:
1. for循環(huán)的速度 比 for中開(kāi)出goroutine并執(zhí)行的速度 執(zhí)行的快
2. 但是 開(kāi)goroutine和執(zhí)行第一個(gè)fmt的速度可能趕上 for循環(huán)的速度 比如前12個(gè)count和count doing
3. 關(guān)鍵問(wèn)題,第二個(gè)for循環(huán)執(zhí)行的fmt竟然要比goroutine中的第二個(gè)fmt快??(放入channel很耗時(shí)?)
4. main結(jié)束時(shí),也就是第二個(gè)for循環(huán)結(jié)束時(shí), 還有g(shù)oroutine中的第二個(gè)fmt沒(méi)執(zhí)行
輸出:
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
補(bǔ)充:【golang】goroutine調(diào)度的坑
今天說(shuō)說(shuō)我遇到的一個(gè)小坑, 關(guān)于goroutine 調(diào)度的問(wèn)題。
關(guān)于goroutine的調(diào)度,網(wǎng)上資料已經(jīng)一大堆了,這里就不再贅述了。
還是簡(jiǎn)單的說(shuō)一下我理解的goroutine的調(diào)度。goroutine是語(yǔ)言層面的,它和內(nèi)核線程是M:N的關(guān)系,并且用了分段棧,是相當(dāng)輕量了。
如果設(shè)置runtime.GOMAXPROCS為1,那么會(huì)有一個(gè)上下文G,在G上會(huì)有一個(gè)對(duì)應(yīng)的內(nèi)核線程M,內(nèi)核線程M上可以對(duì)應(yīng)很多個(gè)goroutine記作G,每個(gè)上下文都會(huì)有一個(gè)隊(duì)列稱作runqueue,在用go關(guān)鍵字開(kāi)啟一個(gè)goroutine的時(shí)候,該goroutine就會(huì)被裝入runqueue中,然后被M用來(lái)執(zhí)行,如果剛好有兩個(gè)goroutine在隊(duì)列里,先執(zhí)行的goroutine因?yàn)閳?zhí)行一些耗時(shí)操作(系統(tǒng)調(diào)用,讀寫(xiě) channel,gosched 主動(dòng)放棄,網(wǎng)絡(luò)IO)會(huì)被掛起(扔到全局runqueue),然后調(diào)度后面的goroutine。
好,重點(diǎn)在這里,看一下下面的一段代碼
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()
}
這段代碼你運(yùn)行,你會(huì)發(fā)現(xiàn),永遠(yuǎn)都會(huì)被阻塞住,hello永遠(yuǎn)都打印不出來(lái)
好,這里出現(xiàn)了兩個(gè)問(wèn)題
1.為什么死循環(huán)的goroutine總是先運(yùn)行?按理說(shuō)不應(yīng)該是隨機(jī)的嗎?
2.為什么死循環(huán)的goroutine會(huì)阻塞而沒(méi)有被掛起?
先看第二個(gè)問(wèn)題。這里的話,我當(dāng)時(shí)也很苦惱,于是在網(wǎng)上發(fā)了問(wèn)題,得到的回復(fù)是,死循環(huán)不屬于上述任何一種需要被掛起的狀態(tài),于是死循環(huán)的goroutine會(huì)一直運(yùn)行,想象一個(gè)高并發(fā)的場(chǎng)景,如果其中一個(gè)goroutine因?yàn)槟撤N原因陷入死循環(huán)了,當(dāng)前執(zhí)行這個(gè)goroutine的OS thread基本就會(huì)被一直執(zhí)行這個(gè)goroutine,直到程序結(jié)束,這簡(jiǎn)直是一場(chǎng)災(zāi)難。但是,1.12 會(huì)修正這個(gè)小問(wèn)題。我們還是默默的等待新版本發(fā)布吧。
再看第一個(gè)問(wèn)題。為什么死循環(huán)的goroutine總是先運(yùn)行?按理說(shuō)不應(yīng)該是隨機(jī)的嗎?測(cè)試過(guò)很多次,都是第二個(gè)goroutine先運(yùn)行。嗯,其實(shí)就算是第二個(gè)goroutine先運(yùn)行也是具有隨機(jī)性的,這關(guān)于golang的編譯器如何去實(shí)現(xiàn)隨機(jī)。看一下大佬的回答 :
不是說(shuō)測(cè)試很多遍它就會(huì)一直這樣,語(yǔ)言規(guī)范沒(méi)有說(shuō)必須是這個(gè)順序,那編譯器怎么實(shí)現(xiàn)都可以,因?yàn)槎疾贿`反規(guī)范。
所以你要把它看作是隨機(jī)的,不能依賴這種未確定的行為,不然很可能新版的編譯器就會(huì)破壞你依賴的事實(shí)。有些項(xiàng)目不敢升級(jí)編譯器版本,就是因?yàn)橐蕾嚵颂囟ò姹镜木幾g器的行為,一升級(jí)就壞了。
不是你自己測(cè)試很多遍你就能依賴它,編譯器、操作系統(tǒng)、硬件等等不同,都有可能出現(xiàn)不同的結(jié)果。可以依賴的只有語(yǔ)言規(guī)范( https://golang.org/ref/spec ),編譯器實(shí)現(xiàn)者是一定會(huì)遵守的。
到這里也算是解決了上述的兩個(gè)問(wèn)題了。
來(lái)看一下另外一個(gè)版本
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()
}
執(zhí)行結(jié)果是,會(huì)先打印一個(gè)hello,然后陷入死循環(huán),這也是說(shuō)明了goroutine在遇到耗時(shí)操作或者系統(tǒng)調(diào)用的時(shí)候,后面的代碼都不會(huì)執(zhí)行了(request successful 沒(méi)有被打印),會(huì)被拋到全局runqueue里去,然后執(zhí)行runqueue中等待的goroutine
希望能夠幫助和我一樣正在學(xué)習(xí)golang的友軍們更好的理解goroutine的調(diào)度問(wèn)題
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。
您可能感興趣的文章:- golang中for循環(huán)遍歷channel時(shí)需要注意的問(wèn)題詳解
- golang實(shí)現(xiàn)基于channel的通用連接池詳解
- Golang優(yōu)雅關(guān)閉channel的方法示例
- golang中單向channel的語(yǔ)法介紹
- golang gin 框架 異步同步 goroutine 并發(fā)操作
- GOLANG使用Context管理關(guān)聯(lián)goroutine的方法
- 關(guān)于golang利用channel和goroutine完成統(tǒng)計(jì)素?cái)?shù)的思路