原创文章,欢迎转载,转载请注明出处,谢谢。


0. 前言

Go runtime 调度器精讲(三):main goroutine 创建 介绍了 main goroutine 的创建,文中我们说 main goroutine 和非 main goroutine 有区别。当时卖了个关子并未往下讲,这一讲我们会继续介绍非 main goroutine (也就是 go 关键字创建的 goroutine,后文统称为 gp) 的运行,并且把这个关子解开,说一说它们的区别在哪儿。

1. gp 的创建

首先看一个示例:

func g2() {
time.Sleep(10 * time.Second)
println("hello world")
} func main() {
go g2() time.Sleep(1 * time.Minute)
println("main exit")
}

main 函数创建两个 goroutine,一个 main goroutine,一个普通 goroutine。从 Go runtime 调度器精讲(四):运行 main goroutine 可知 main goroutine 运行完之后就调用 exit(0) 退出了。为了能进入 gp,我们这里在 main goroutine 中加了 1 分钟的等待时间。

Go runtime 的启动在前几讲都有介绍,这里直接进入 main 函数,查看 gp 是如何创建的:

(dlv) c
> main.main() ./goexit.go:12 (hits goroutine(1):1 total:1) (PC: 0x46238a)
7: func g2() {
8: time.Sleep(10 * time.Second)
9: println("hello world")
10: }
11:
=> 12: func main() {
13: go g2()
14:
15: time.Sleep(30 * time.Minute)
16: println("main exit")
17: }

直接看 main 函数,我们看不出 go 关键字做了什么,查看 CPU 的汇编指令:

(dlv) si
> main.main() ./goexit.go:13 (PC: 0x462395)
goexit.go:12 0x462384 7645 jbe 0x4623cb
goexit.go:12 0x462386 55 push rbp
goexit.go:12 0x462387 4889e5 mov rbp, rsp
goexit.go:12 0x46238a* 4883ec10 sub rsp, 0x10
goexit.go:13 0x46238e 488d050b7a0100 lea rax, ptr [rip+0x17a0b]
=> goexit.go:13 0x462395 e8c6b1fdff call $runtime.newproc
goexit.go:15 0x46239a 48b800505c18a3010000 mov rax, 0x1a3185c5000
goexit.go:15 0x4623a4 e8b79fffff call $time.Sleep

可以看到,go 关键字被编译转换后实际调用的是 $runtime.newproc 函数,这个函数在 Go runtime 调度器精讲(四):运行 main goroutine 已经非常详细的介绍过了,这里就不赘述了。

有必要在说明的是,main goroutine 和普通 goroutine 执行的顺序。当调用 runtime.newproc 后,gp 被添加到 P 的可运行队列(如果队列满,被添加到全局队列),接着线程会调度运行该 gp。不过对于 newproc 来说,gp 放入队列后,newproc 就退出了。接着执行后续的 main goroutine 代码。

如果此时 gp 未运行或者未结束,并且 main goroutine 未等待/阻塞的话,main goroutine 将直接退出。

2. gp 的退出

前面说 gp 和 main goroutine 的区别主要体现在 goroutine 的退出这里。main goroutine 的退出比较残暴,直接调用 exit(0) 退出进程。那么,gp 是怎么退出的呢?

我们在 g2 结束点处打断点,看看 g2 是怎么退出的:

(dlv) b ./goexit.go:10
Breakpoint 1 set at 0x46235b for main.g2() ./goexit.go:10
(dlv) c
hello world
> main.g2() ./goexit.go:10 (hits goroutine(5):1 total:1) (PC: 0x46235b)
7: func g2() {
8: time.Sleep(10 * time.Second)
9: println("hello world")
=> 10: }
11:
12: func main() {
13: go g2()
14:
15: time.Sleep(30 * time.Minute)
(dlv) si
> main.g2() ./goexit.go:10 (PC: 0x46235f)
goexit.go:9 0x462345 488d05b81b0100 lea rax, ptr [rip+0x11bb8]
goexit.go:9 0x46234c bb0c000000 mov ebx, 0xc
goexit.go:9 0x462351 e88a30fdff call $runtime.printstring
goexit.go:9 0x462356 e86528fdff call $runtime.printunlock
goexit.go:10 0x46235b* 4883c410 add rsp, 0x10
=> goexit.go:10 0x46235f 5d pop rbp
goexit.go:10 0x462360 c3 ret
goexit.go:7 0x462361 e89ab1ffff call $runtime.morestack_noctxt
goexit.go:7 0x462366 ebb8 jmp $main.g2

CPU 执行指令到 pop rbp,接着执行 ret:

        goexit.go:10    0x46235f        5d              pop rbp
=> goexit.go:10 0x462360 c3 ret
goexit.go:7 0x462361 e89ab1ffff call $runtime.morestack_noctxt
goexit.go:7 0x462366 ebb8 jmp $main.g2
(dlv) si
> runtime.goexit() /usr/local/go/src/runtime/asm_amd64.s:1651 (PC: 0x45d7a1)
Warning: debugging optimized function
TEXT runtime.goexit(SB) /usr/local/go/src/runtime/asm_amd64.s
asm_amd64.s:1650 0x45d7a0 90 nop
=> asm_amd64.s:1651 0x45d7a1 e8ba250000 call $runtime.goexit1
asm_amd64.s:1653 0x45d7a6 90 nop

我们看到了什么,执行 ret 直接跳转到了 call $runtime.goexit1。还记得在 Go runtime 调度器精讲(三):main goroutine 创建 中说每个 goroutine 栈都会在“栈顶”放 funcPC(goexit) + 1 的地址。这里实际是做了一个偷梁换柱,gp 的栈在退出执行 ret 时都会跳转到 call $runtime.goexit1 继续执行。

进入 runtime.goexit1

// Finishes execution of the current goroutine.
func goexit1() {
...
mcall(goexit0) // mcall 会切换当前栈到 g0 栈,接着在 g0 栈执行 goexit0
}

实际执行的是 goexit0

// goexit continuation on g0.
func goexit0(gp *g) {
mp := getg().m // 这里是 g0 栈,mp = m0
pp := mp.p.ptr() // m0 绑定的 P casgstatus(gp, _Grunning, _Gdead) // 将 gp 的状态更新为 _Gdead
gp.m = nil // 将 gp 绑定的线程更新为 nil,和线程解绑
... dropg() // 将当前线程和 gp 解绑
...
gfput(pp, gp) // 退出的 gp 还是可以重用的,gfput 将 gp 放到本地或者全局空闲队列中 ...
schedule() // 线程执行完一个 gp 还没有退出,继续进入 schedule 找 goroutine 执行
}

gp 退出了,线程并没有退出,线程将 gp 安顿好之后,继续开始新一轮调度,真是劳模啊。

3. 小结

本讲介绍了用 go 关键字创建的 goroutine 是如何运行的,下一讲我们放松放松,看几个案例分析调度器的行为。


Go runtime 调度器精讲(六):非 main goroutine 运行的更多相关文章

  1. 非main goroutine的退出及调度循环(15)

    本文是<Go语言调度器源代码情景分析>系列的第15篇,也是第二章的第5小节. 上一节我们说过main goroutine退出时会直接执行exit系统调用退出整个进程,而非main goro ...

  2. 第三百五十二节,Python分布式爬虫打造搜索引擎Scrapy精讲—chrome谷歌浏览器无界面运行、scrapy-splash、splinter

    第三百五十二节,Python分布式爬虫打造搜索引擎Scrapy精讲—chrome谷歌浏览器无界面运行.scrapy-splash. splinter 1.chrome谷歌浏览器无界面运行 chrome ...

  3. Python_装饰器精讲_33

    from functools import wraps def wrapper(func): #func = holiday @wraps(func) def inner(*args,**kwargs ...

  4. mybatis精讲(六)--二级缓存

    目录 简介 配置 源码 CachingExecutor 自定义二级缓存 # 加入战队 微信公众号 简介 上一章节我们简单了解了二级缓存的配置.今天我们详细分析下二级缓存以及为什么不建议使用二级缓存. ...

  5. 三十一 Python分布式爬虫打造搜索引擎Scrapy精讲—chrome谷歌浏览器无界面运行、scrapy-splash、splinter

    1.chrome谷歌浏览器无界面运行 chrome谷歌浏览器无界面运行,主要运行在Linux系统,windows系统下不支持 chrome谷歌浏览器无界面运行需要一个模块,pyvirtualdispl ...

  6. go调度: 第二部分-go调度器

    前言 这个博客是三部分中提供go调度器的语义和机制的部分. 博客三部分的顺序: 1) go调度: 第一部分-操作系统调度 2) go调度: 第二部分-go调度器 3) go调度: 第三部分-并发 介绍 ...

  7. Linux调度器 - deadline调度器

    一.概述 实时系统是这样的一种计算系统:当事件发生后,它必须在确定的时间范围内做出响应.在实时系统中,产生正确的结果不仅依赖于系统正确的逻辑动作,而且依赖于逻辑动作的时序.换句话说,当系统收到某个请求 ...

  8. goroutine与调度器

    29 November 2013 by skoo 我们都知道Go语言是原生支持语言级并发的,这个并发的最小逻辑单元就是goroutine.goroutine就是Go语言提供的一种用户态线程,当然这种用 ...

  9. linux调度器源码分析 - 概述(一)

    本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 引言 调度器作为操作系统的核心部件,具有非常重要的意义,其随着linux内核的更新也不断进行着更新.本系列文章通 ...

  10. FreeRTOS 任务与调度器(1)

    前言: Task.c和Task.h文件内是FreeRTOS的核心内容,所有任务和调度器相关的API函数都在这个文件中,它包括下图这些内容FreeRTOS文件如下: Task.c和Task.h文件内是F ...

随机推荐

  1. PHP集群session共享

    集群的概念没有多复杂,其实就是多台电脑为了同一个目标在一起工作.在Web应用中,就是多个服务器提供一个站点的服务. 搭建PHP集群的第一步就是设置负载均衡. 默认情况下PHP是将session存在本地 ...

  2. TP3.2与TP5.0的区别

    1. 控制器输出return $this->fetch(); ----5$this->display(); ----3.2单字母函数去掉了 如:M() D() U() S() C() 3. ...

  3. [oeasy]python0051_ 转义_escape_字符_character_单引号_双引号_反引号_ 退格键

    转义字符 回忆上次内容 上次研究的是进制转化 10进制可以转化为其他形式 bin oct hex 其他进制也可以转化为10进制 int 可以设置base来决定转为多少进制 回忆一下 我们为什么会有八进 ...

  4. Vue 使用Use、prototype自定义全局插件

    Vue 使用Use.prototype自定义全局插件   by:授客 QQ:1033553122   开发环境   Win 10   node-v10.15.3-x64.msi 下载地址: https ...

  5. 跟着ChatGPT学习设计模式 - 工厂模式

    1. 前言 在工作过程中,越发觉得设计模式的重要性.经常会有人说工作5年的人,大学生随便培训1-2月也能做同样的工作,没错,大学生的确可以做. 但其写的代码,可维护性.可扩展性.添加新功能时方便还是简 ...

  6. Scratch全套Q版三国人物角色素材包免费下载

    全新Q版三国人物角色素材包,内含142张细腻可爱的Q版风格图片,涵盖三国名将.士兵.场景等丰富元素,为scratch爱好者提供多样选择,适合各类三国主题创作. 免费下载:www.xiaohujing. ...

  7. ComfyUI插件:ComfyUI Impact 节点(二)

    前言: 学习ComfyUI是一场持久战,而 ComfyUI Impact 是一个庞大的模块节点库,内置许多非常实用且强大的功能节点 ,例如检测器.细节强化器.预览桥.通配符.Hook.图片发送器.图片 ...

  8. 使用 addRouteMiddleware 动态添加中间

    title: 使用 addRouteMiddleware 动态添加中间 date: 2024/8/4 updated: 2024/8/4 author: cmdragon excerpt: 摘要:文章 ...

  9. 中国2023年GDP增速5.2%

    在中美贸易战和三年全球疫情的大背景下,我国的经济依旧保持强有力的增速,这表明了经济发展不断转好,一切恢复到疫情和贸易战之前也是有待期望的.

  10. 台式机,华硕主板z390ws,cpu为i7-9700k 安装Ubuntu18.04系统 使用独立显卡工作 (但是显示器HDMI线缆插在主板的HDMI插槽)开机进入系统运行几分钟后自动重启,此时主板显示错误码为AMI错误

    如题: 手上有这样一台新的工作站,配置为华硕主板z390ws,cpu为i7-9700k ,独立显卡为技嘉2060super, 安装Ubuntu18.04系统 . 在主板bios中进行设置(设置使用 P ...