你好,我是小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 了,只能放到全局队列。这下程序的输出顺序就不那么直观了。

所以,记住本文的核心内容就行了:

  1. runnext 的优先级最高。

  2. time.Sleep 在老版本中会创建一个 goroutine,在 1.14(包含)之后不会创建 goroutine 了。

如果被别人考到,知道三级队列,以及 time 包在 1.14 的变更就行了。

好了,这就是今天全部的内容了~ 我是小X,我们下期再见~


欢迎关注曹大的 TechPaper 以及码农桃花源~

曹大带我学 Go(2)—— 迷惑的 goroutine 执行顺序相关推荐

  1. 曹大带我学 Go(6)—— 技术之外

    你好,我是小X. 曹大最近开 Go 课程了,小X 正在和曹大学 Go. 这个系列会讲一些从课程中学到的让人醍醐灌顶的东西,拨云见日,带你重新认识 Go. 有学员私下和我说,这个课程挺打击他的自信心.我 ...

  2. 『曹大带我学 Go 』系列文章汇总

    你好,我是小 X. 之前写了 11 篇跟着曹大学 Go 的文章,今天来汇总一下. 曹大的功力深厚,但能学到多少全看自己.第一期 Go 训练营也早就结束了,但学习还得继续.后面我也会继续发布这个系列,希 ...

  3. 曹大带我学 Go(8)—— 一个打点引发的事故

    你好,我是小X. 曹大最近开 Go 课程了,小X 正在和曹大学 Go. 这个系列会讲一些从课程中学到的让人醍醐灌顶的东西,拨云见日,带你重新认识 Go. 最近线上事故频发,搞得焦头烂额,但是能用上跟曹 ...

  4. 曹大带我学 Go(12)—— 面向火焰图编程

    你好,我是小X. 曹大最近开 Go 课程了,小X 正在和曹大学 Go. 这个系列会讲一些从课程中学到的让人醍醐灌顶的东西,拨云见日,带你重新认识 Go. 现实中听过各种面向 XX 编程,什么面向过程编 ...

  5. 曹大带我学 Go(11)—— 从 map 的 extra 字段谈起

    你好,我是小X. 曹大最近开 Go 课程了,小X 正在和曹大学 Go. 这个系列会讲一些从课程中学到的让人醍醐灌顶的东西,拨云见日,带你重新认识 Go. 熟悉 map 结构体的读者应该知道,hmap ...

  6. 曹大带我学 Go(10)—— 如何给 Go 提性能优化的 pr

    你好,我是小X. 曹大最近开 Go 课程了,小X 正在和曹大学 Go. 这个系列会讲一些从课程中学到的让人醍醐灌顶的东西,拨云见日,带你重新认识 Go. 之前 qcrao 写了一篇<成为 Go ...

  7. 曹大带我学 Go(9)—— 开始积累自己的工具库

    你好,我是小X. 曹大最近开 Go 课程了,小X 正在和曹大学 Go. 这个系列会讲一些从课程中学到的让人醍醐灌顶的东西,拨云见日,带你重新认识 Go. 不知道你有没有这样的经验:看了很多计算机相关的 ...

  8. 曹大带我学 Go(7)—— 如何优雅地指定配置项

    你好,我是小X. 曹大最近开 Go 课程了,小X 正在和曹大学 Go. 这个系列会讲一些从课程中学到的让人醍醐灌顶的东西,拨云见日,带你重新认识 Go. 最近一个年久失修的库导致了线上事故,不得不去做 ...

  9. 曹大带我学 Go(5)—— 哪里来的 goexit

    你好,我是小X. 曹大最近开 Go 课程了,小X 正在和曹大学 Go. 这个系列会讲一些从课程中学到的让人醍醐灌顶的东西,拨云见日,带你重新认识 Go. 在学员群里,有同学在用 dlv 调试时看到了令 ...

最新文章

  1. Ktor 1.0发布:JetBrains推出的Kotlin Web框架
  2. HDU 4946 Area of Mushroom 凸包
  3. 通过jstack定位在线运行java系统故障_案例1
  4. java:迭代器Iterator
  5. 初谈Git(本机克隆项目远程仓库)
  6. POJ - 2689 Prime Distance(素数区间筛模板)
  7. 信息网络传播权保护条例(2006)
  8. C++设计模式之策略模式(Strategy)
  9. [WPF]ListView点击列头排序功能实现
  10. android5.1 显示方向,Android5.1 Settings.apk定制显示选项
  11. Shell——test 命令
  12. koa2-cors应答跨域请求实现
  13. PHP 显示信息到控制台console
  14. CentOS中nginx负载均衡和反向代理的搭建
  15. __strong、__weak 与 __unsafe_unretained区别
  16. myeclipse 7.5 for linux and windows
  17. 【随笔】Java团长
  18. U8C报表模板已设置,任务已分配仍无法查看报表数据
  19. 你好Linux!第一篇——Linux的前世今生和应用
  20. fx5u模拟量如何读取_三菱fx5u模拟量输入接线

热门文章

  1. R语言学习笔记4_参数估计
  2. java设计模式 之 模板方法模式
  3. 微信小程序常用视图容器组件
  4. 一文熟悉 Go 的循环结构 —— for 循环
  5. 新闻发布系统——INSERT 语句与 FOREIGN KEY 约束XXX冲突。该冲突发生于数据库XXX,表XXX, column 'XXX。
  6. Temu拼多多跨境店铺如何快速上货?
  7. Google Maps谷歌地图车辆位置描点、位置详情查看
  8. 光猫、路由器、交换机如何工作的
  9. 怎样卸载计算机更新程序,windows10升级程序卸载怎么操作_windows10升级程序怎样卸载删除-win7之家...
  10. 教程篇(6.0) 01. FortiGate及其Security Fabric介绍 ❀ FortiGate 安全 ❀ Fortinet 网络安全专家 NSE 4