解決Golang中g(shù)oroutine執(zhí)行速度的問(wèn)題
突然想到了之前一直沒(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ì)列稱(chēng)作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é)果??梢砸蕾嚨闹挥姓Z(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ò)誤或未考慮完全的地方,望不吝賜教。
相關(guān)文章
Golang設(shè)計(jì)模式工廠模式實(shí)戰(zhàn)寫(xiě)法示例詳解
這篇文章主要為大家介紹了Golang 工廠模式實(shí)戰(zhàn)寫(xiě)法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08golang使用通道時(shí)需要注意的一些問(wèn)題
本文主要介紹了golang使用通道時(shí)需要注意的一些問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07Go語(yǔ)言實(shí)現(xiàn)一個(gè)Http?Server框架(一)?http庫(kù)的使用
本文主要介紹用Go語(yǔ)言實(shí)現(xiàn)一個(gè)Http?Server框架中對(duì)http庫(kù)的基本使用說(shuō)明,文中有詳細(xì)的代碼示例,感興趣的同學(xué)可以借鑒一下2023-04-04Golang 如何實(shí)現(xiàn)函數(shù)的任意類(lèi)型傳參
這篇文章主要介紹了Golang 實(shí)現(xiàn)函數(shù)的任意類(lèi)型傳參操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-04-04Go語(yǔ)言學(xué)習(xí)之JSON編碼解析與使用
這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言中JSON編碼的解析與使用已經(jīng)JSON與Map、結(jié)構(gòu)體的互相轉(zhuǎn)化,文中的示例代碼講解詳細(xì),需要的可以參考一下2023-02-02golang 實(shí)現(xiàn)interface{}轉(zhuǎn)其他類(lèi)型操作
這篇文章主要介紹了golang 實(shí)現(xiàn)interface{}轉(zhuǎn)其他類(lèi)型操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12Golang Http 驗(yàn)證碼示例實(shí)現(xiàn)
這篇文章主要介紹了Golang Http 驗(yàn)證碼示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08