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


Posted in Golang onMay 02, 2021

突然想到了之前一直没留意的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的调度问题

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

Golang 相关文章推荐
Go各时间字符串使用解析
Apr 02 Golang
为什么不建议在go项目中使用init()
Apr 12 Golang
golang中切片copy复制和等号复制的区别介绍
Apr 27 Golang
golang中的空slice案例
Apr 27 Golang
解决go在函数退出后子协程的退出问题
Apr 30 Golang
golang 实现时间戳和时间的转化
May 07 Golang
golang操作rocketmq的示例代码
Apr 06 Golang
Go归并排序算法的实现方法
Apr 06 Golang
Golang 对es的操作实例
Apr 20 Golang
Golang 切片(Slice)实现增删改查
Apr 22 Golang
Go Grpc Gateway兼容HTTP协议文档自动生成网关
Jun 16 Golang
在ubuntu下安装go开发环境的全过程
Aug 05 Golang
解决golang结构体tag编译错误的问题
May 02 #Golang
golang 实现Location跳转方式
May 02 #Golang
解决golang post文件时Content-Type出现的问题
May 02 #Golang
对Golang中的FORM相关字段理解
May 02 #Golang
解决go在函数退出后子协程的退出问题
Apr 30 #Golang
Go语言 go程释放操作(退出/销毁)
golang DNS服务器的简单实现操作
You might like
全国FM电台频率大全 - 24 贵州省
2020/03/11 无线电
第三节 定义一个类 [3]
2006/10/09 PHP
PHP HTML代码串 截取实现代码
2009/06/29 PHP
PHP 验证码的实现代码
2011/07/17 PHP
PHP的switch判断语句的“高级”用法详解
2014/10/01 PHP
PHP 实现类似js中alert() 提示框
2015/03/18 PHP
php封装的验证码工具类完整实例
2016/10/19 PHP
PHP使用递归按层级查找数据的方法
2019/11/10 PHP
利用js实现选项卡的特别效果的实例
2013/03/03 Javascript
如何在node的express中使用socket.io
2014/12/15 Javascript
jquery 实现滚动条下拉时无限加载的简单实例
2016/06/01 Javascript
VUEJS实战之构建基础并渲染出列表(1)
2016/06/13 Javascript
Vue项目webpack打包部署到服务器的实例详解
2017/07/17 Javascript
详解如何使用webpack在vue项目中写jsx语法
2017/11/08 Javascript
通过JS深度判断两个对象字段相同
2019/06/14 Javascript
es6中比较有用的7个技巧小结
2019/07/12 Javascript
[01:02:18]VGJ.S vs infamous Supermajor 败者组 BO3 第一场 6.4
2018/06/05 DOTA
Python绘制七段数码管实例代码
2017/12/20 Python
Python爬虫使用Selenium+PhantomJS抓取Ajax和动态HTML内容
2018/02/23 Python
python验证码识别教程之滑动验证码
2018/06/04 Python
pandas 小数位数 精度的处理方法
2018/06/09 Python
python爬取盘搜的有效链接实现代码
2019/07/20 Python
Python要求O(n)复杂度求无序列表中第K的大元素实例
2020/04/02 Python
Pytorch 解决自定义子Module .cuda() tensor失败的问题
2020/06/23 Python
Keras构建神经网络踩坑(解决model.predict预测值全为0.0的问题)
2020/07/07 Python
博朗(Braun)俄罗斯官方商店:德国小家电品牌
2019/09/24 全球购物
前台领班岗位职责
2013/12/04 职场文书
信息技术专业个人自我评价
2013/12/11 职场文书
跳槽求职信范文
2014/05/26 职场文书
党支部三严三实对照检查材料思想汇报
2014/09/29 职场文书
淘宝客服专员岗位职责
2015/04/07 职场文书
六一晚会主持词开场白
2015/05/28 职场文书
2016国培学习心得体会
2016/01/08 职场文书
Nginx访问日志及错误日志参数说明
2021/03/31 Servers
Python源码解析之List
2021/05/21 Python
Nginx反向代理学习实例教程
2021/10/24 Servers