您的位置:首页技术文章

解决Golang中goroutine执行速度的问题

【字号: 日期:2023-10-20 16:00:27浏览:2作者:馨心

突然想到了之前一直没留意的for循环中开goroutine的执行顺序问题,就找了段代码试了试,试了几次后发现几个有意思的地方,我暂时没有精力往更深处挖掘,希望有golang大神能简单说一说这几个地方是怎么回事。

代码:

package main import 'fmt' func Count(ch chan int) {fmt.Println('Count doing')ch <- 1fmt.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 {<-chfmt.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 0Count 1Count 2Count 3Count 4Count 5Count 6Count 7Count 8Count 9Count 10Count 11Count doingCount doingCount doingCount doingCount doingCount 12Count doingCount doingCount doingCount doingCount doingCount doingCount doingCount 13Count 14Count 15Count 16Count 17Count 18Count 19Count 20Count 21Count doingCount doingCount doingCount 22Count doingCount doingCount doingCount 23Count 24Count 25Count 26Count 27Count 28Count 29Count 30Count doingCount 31Count doingCount doingCount 32Count 33Count 34Count 35Count doingCount 36Count doingCount doingCount 37Count 38Count doingCount doingCount doingCount doingCount 39Count 40Count 41Count 42Count 43Count doingCount doingCount 44Count 45Count 46Count 47Count doingCount 48Count 49Count doingCount doingCount 50Count 51Count doingCount doingCount doingCount doingCount doingCount 52Count 53Count doingCount doingCount doingCount doingCount 54Count doingCount 55Count 56Count 57Count 58Count 59Count 60Count 61Count 62Count 63Count 64Count 65Count doingCount doingCount doingCount 66Count 67Count 68Count 69Count doingCount 70Count doingCount 71Count 72Count doingCount 73Count doingCount doingCount 74Count doingCount 75Count 76Count 77Count doingCount doingCount doingCount doingCount 78Count 79Count 80Count 81Count 82Count 83Count 84Count 85Count 86Count 87Count 88Count 89Count 90Count 91Count 92Count 93Count 94Count doingCount doingCount doingCount doingCount doingCount doingCount doingCount doingCount 95Count doingCount 96Count doingCount 97Count 98Count doingCount 99Count doingCount doingCounting 0Counting 1Counting 2Counting 3Counting 4Counting 5Counting 6Count doingCount doingCounting 7Counting 8Count doingCountingCount doingCounting 9CountingCount doingCount doingCount doingCount doingCount doingCountingCount doingCount doingCount doingCountingCount doingCountingCount doingCounting 10Counting 11CountingCount doingCount doingCount doingCount doingCount doingCount doingCountingCount doingCount doingCountingCountingCount doingCount doingCount doingCount doingCountingCount doingCountingCount doingCount doingCounting 12Counting 13Counting 14Counting 15Counting 16Counting 17Counting 18Counting 19Counting 20Counting 21Counting 22Counting 23Counting 24Counting 25Counting 26Counting 27Counting 28Counting 29Counting 30Counting 31Counting 32Counting 33Counting 34Counting 35Counting 36Counting 37Counting 38Counting 39Counting 40Counting 41Counting 42Counting 43Counting 44Counting 45Counting 46Counting 47Counting 48Counting 49Counting 50Counting 51Counting 52Counting 53Counting 54Counting 55Counting 56CountingCountingCountingCountingCountingCountingCount doingCountingCount doingCountingCountingCounting 57Counting 58Counting 59Counting 60Counting 61Counting 62Counting 63Counting 64Counting 65Counting 66Counting 67Counting 68Counting 69Counting 70Counting 71Counting 72Counting 73Counting 74Counting 75Counting 76CountingCountingCountingCountingCountingCountingCountingCountingCountingCountingCountingCountingCountingCountingCountingCountingCountingCountingCountingCounting 77Counting 78Counting 79Counting 80Counting 81Counting 82Counting 83Counting 84Counting 85Counting 86Counting 87Counting 88Counting 89Counting 90Counting 91Counting 92Counting 93Counting 94Counting 95Counting 96Counting 97Counting 98Counting 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的调度问题

以上为个人经验,希望能给大家一个参考,也希望大家多多支持优爱好网。如有错误或未考虑完全的地方,望不吝赐教。

标签: Golang
相关文章: