曹大带我学 Go(2)—— 迷惑的 goroutine 执行顺序
你好,我是小X。
曹大最近开 Go 课程了,小X 正在和曹大学 Go。
这个系列会讲一些从课程中学到的让人醍醐灌顶的东西,拨云见日,带你重新认识 Go。
上一篇文章我们讲了 Go 调度的本质是一个生产-消费流程。
生产端是正在运行的 goroutine 执行 go func(){}()
语句生产出 goroutine 并塞到三级队列中去。
消费端则是 Go 进程中的 m 在不断地执行调度循环,从三级队列中拿到 goroutine 来运行。
今天我们来通过 2 个实际的代码例子来看看 goroutine 的执行顺序是怎样的。
第一个例子
首先来看第一个例子:
package mainimport ("fmt""runtime""time"
)func main() {runtime.GOMAXPROCS(1)for i := 0; i < 10; i++ {i := igo func() {fmt.Println(i)}()}var ch = make(chan int)<- ch
}
首先通过 runtime.GOMAXPROCS(1)
设置只有一个 P,接着创建了 10 个 goroutine,并分别打印出 i
值。你可以先想一下输出会是什么,再对着答案会有更深入的理解。
揭晓答案:
9
0
1
2
3
4
5
6
7
8
fatal error: all goroutines are asleep - deadlock!goroutine 1 [chan receive]:
main.main()/home/raoquancheng/go/src/hello/main.go:16 +0x96
exit status 2
程序输出的 fatal error
是因为 main goroutine 正在从一个 channel 里读数据,而这时所有的 channel 都已经挂了,因此出现死锁。这里先忽略这个,只需要关注 i
输出的顺序:9, 0, 1, 2, 3, 4, 5, 6, 7, 8
。
我来解释一下原因:因为一开始就设置了只有一个 P,所以 for 循环里面“生产”出来的 goroutine 都会进入到 P 的 runnext 和本地队列,而不会涉及到全局队列。
每次生产出来的 goroutine 都会第一时间塞到 runnext,而 i 从 1 开始,runnext 已经有 goroutine 在了,所以这时会把 old goroutine 移动 P 的本队队列中去,再把 new goroutine 放到 runnext。之后会重复这个过程……
因此这后当一次 i 为 9 时,新 goroutine 被塞到 runnext,其余 goroutine 都在本地队列。
之后,main goroutine 执行了一个读 channel 的语句,这是一个好的调度时机:main goroutine 挂起,运行 P 的 runnext 和本地可运行队列里的 gorotuine。
而我们又知道,runnext 里的 goroutine 的执行优先级是最高的,因此会先打印出 9,接着再执行本地队列中的 goroutine 时,按照先进先出的顺序打印:0, 1, 2, 3, 4, 5, 6, 7, 8
。
是不是非常有意思?
第二个例子
别急,我们再来看第 2 个例子:
package mainimport ("fmt""runtime""time"
)func main() {runtime.GOMAXPROCS(1)for i := 0; i < 10; i++ {i := igo func() {fmt.Println(i)}()}time.Sleep(time.Hour)
}
和第一个例子的不同之处是我们把读 channel 的代码换成 Sleep 操作。这一次,你还能正确回答 i
的输出顺序是什么吗?
我们直接揭晓答案。
当我们用 go1.13 运行时:
$ go1.13.8 run main.go0
1
2
3
4
5
6
7
8
而当我们用 go1.14 及之后的版本运行时:
$ go1.14 run main.go9
0
1
2
3
4
5
6
7
8
可以看到,用 go1.14 及之后的版本运行时,输出顺序和之前的一致。而用 go1.13 运行时,却先输出了 0
,这又是什么原因呢?
这就要从 Go 1.14 修改了 timer 的实现开始说起了。
go 1.13
的 time 包会生产一个名字叫 timerproc 的 goroutine 出来,它专门用于唤醒挂在 timer 上的时间未到期的 goroutine;因此这个 goroutine 会把 runnext 上的 goroutine 挤出去。因此输出顺序就是:0, 1, 2, 3, 4, 5, 6, 7, 8, 9
。
而 go 1.14
把这个唤醒的 goroutine 干掉了,取而代之的是,在调度循环的各个地方、sysmon 里都是唤醒 timer 的代码,timer 的唤醒更及时了,但代码也更难看懂了。所以,输出顺序和第一个例子是一致的。
总结
今天通过 2 个实际的例子再次复习了 Go 调度消费端的流程,也学到了 time 包在不同 go 版本下的不同之处以及它对程序输出造成的影响。
有些人还会把例子中的 10 改成比 256 更大的数去尝试。曹大说这是考眼力,不要给自己找事。因为这时 P 的本地队列装不下这么多 goroutine 了,只能放到全局队列。这下程序的输出顺序就不那么直观了。
所以,记住本文的核心内容就行了:
runnext 的优先级最高。
time.Sleep 在老版本中会创建一个 goroutine,在 1.14(包含)之后不会创建 goroutine 了。
如果被别人考到,知道三级队列,以及 time 包在 1.14 的变更就行了。
好了,这就是今天全部的内容了~ 我是小X,我们下期再见~
欢迎关注曹大的 TechPaper 以及码农桃花源~
曹大带我学 Go(2)—— 迷惑的 goroutine 执行顺序相关推荐
- 曹大带我学 Go(6)—— 技术之外
你好,我是小X. 曹大最近开 Go 课程了,小X 正在和曹大学 Go. 这个系列会讲一些从课程中学到的让人醍醐灌顶的东西,拨云见日,带你重新认识 Go. 有学员私下和我说,这个课程挺打击他的自信心.我 ...
- 『曹大带我学 Go 』系列文章汇总
你好,我是小 X. 之前写了 11 篇跟着曹大学 Go 的文章,今天来汇总一下. 曹大的功力深厚,但能学到多少全看自己.第一期 Go 训练营也早就结束了,但学习还得继续.后面我也会继续发布这个系列,希 ...
- 曹大带我学 Go(8)—— 一个打点引发的事故
你好,我是小X. 曹大最近开 Go 课程了,小X 正在和曹大学 Go. 这个系列会讲一些从课程中学到的让人醍醐灌顶的东西,拨云见日,带你重新认识 Go. 最近线上事故频发,搞得焦头烂额,但是能用上跟曹 ...
- 曹大带我学 Go(12)—— 面向火焰图编程
你好,我是小X. 曹大最近开 Go 课程了,小X 正在和曹大学 Go. 这个系列会讲一些从课程中学到的让人醍醐灌顶的东西,拨云见日,带你重新认识 Go. 现实中听过各种面向 XX 编程,什么面向过程编 ...
- 曹大带我学 Go(11)—— 从 map 的 extra 字段谈起
你好,我是小X. 曹大最近开 Go 课程了,小X 正在和曹大学 Go. 这个系列会讲一些从课程中学到的让人醍醐灌顶的东西,拨云见日,带你重新认识 Go. 熟悉 map 结构体的读者应该知道,hmap ...
- 曹大带我学 Go(10)—— 如何给 Go 提性能优化的 pr
你好,我是小X. 曹大最近开 Go 课程了,小X 正在和曹大学 Go. 这个系列会讲一些从课程中学到的让人醍醐灌顶的东西,拨云见日,带你重新认识 Go. 之前 qcrao 写了一篇<成为 Go ...
- 曹大带我学 Go(9)—— 开始积累自己的工具库
你好,我是小X. 曹大最近开 Go 课程了,小X 正在和曹大学 Go. 这个系列会讲一些从课程中学到的让人醍醐灌顶的东西,拨云见日,带你重新认识 Go. 不知道你有没有这样的经验:看了很多计算机相关的 ...
- 曹大带我学 Go(7)—— 如何优雅地指定配置项
你好,我是小X. 曹大最近开 Go 课程了,小X 正在和曹大学 Go. 这个系列会讲一些从课程中学到的让人醍醐灌顶的东西,拨云见日,带你重新认识 Go. 最近一个年久失修的库导致了线上事故,不得不去做 ...
- 曹大带我学 Go(5)—— 哪里来的 goexit
你好,我是小X. 曹大最近开 Go 课程了,小X 正在和曹大学 Go. 这个系列会讲一些从课程中学到的让人醍醐灌顶的东西,拨云见日,带你重新认识 Go. 在学员群里,有同学在用 dlv 调试时看到了令 ...
最新文章
- Ktor 1.0发布:JetBrains推出的Kotlin Web框架
- HDU 4946 Area of Mushroom 凸包
- 通过jstack定位在线运行java系统故障_案例1
- java:迭代器Iterator
- 初谈Git(本机克隆项目远程仓库)
- POJ - 2689 Prime Distance(素数区间筛模板)
- 信息网络传播权保护条例(2006)
- C++设计模式之策略模式(Strategy)
- [WPF]ListView点击列头排序功能实现
- android5.1 显示方向,Android5.1 Settings.apk定制显示选项
- Shell——test 命令
- koa2-cors应答跨域请求实现
- PHP 显示信息到控制台console
- CentOS中nginx负载均衡和反向代理的搭建
- __strong、__weak 与 __unsafe_unretained区别
- myeclipse 7.5 for linux and windows
- 【随笔】Java团长
- U8C报表模板已设置,任务已分配仍无法查看报表数据
- 你好Linux!第一篇——Linux的前世今生和应用
- fx5u模拟量如何读取_三菱fx5u模拟量输入接线
热门文章
- R语言学习笔记4_参数估计
- java设计模式 之 模板方法模式
- 微信小程序常用视图容器组件
- 一文熟悉 Go 的循环结构 —— for 循环
- 新闻发布系统——INSERT 语句与 FOREIGN KEY 约束XXX冲突。该冲突发生于数据库XXX,表XXX, column 'XXX。
- Temu拼多多跨境店铺如何快速上货?
- Google Maps谷歌地图车辆位置描点、位置详情查看
- 光猫、路由器、交换机如何工作的
- 怎样卸载计算机更新程序,windows10升级程序卸载怎么操作_windows10升级程序怎样卸载删除-win7之家...
- 教程篇(6.0) 01. FortiGate及其Security Fabric介绍 ❀ FortiGate 安全 ❀ Fortinet 网络安全专家 NSE 4