Go runtime 调度器精讲(六):非 main goroutine 运行
原创文章,欢迎转载,转载请注明出处,谢谢。
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 运行的更多相关文章
- 非main goroutine的退出及调度循环(15)
本文是<Go语言调度器源代码情景分析>系列的第15篇,也是第二章的第5小节. 上一节我们说过main goroutine退出时会直接执行exit系统调用退出整个进程,而非main goro ...
- 第三百五十二节,Python分布式爬虫打造搜索引擎Scrapy精讲—chrome谷歌浏览器无界面运行、scrapy-splash、splinter
第三百五十二节,Python分布式爬虫打造搜索引擎Scrapy精讲—chrome谷歌浏览器无界面运行.scrapy-splash. splinter 1.chrome谷歌浏览器无界面运行 chrome ...
- Python_装饰器精讲_33
from functools import wraps def wrapper(func): #func = holiday @wraps(func) def inner(*args,**kwargs ...
- mybatis精讲(六)--二级缓存
目录 简介 配置 源码 CachingExecutor 自定义二级缓存 # 加入战队 微信公众号 简介 上一章节我们简单了解了二级缓存的配置.今天我们详细分析下二级缓存以及为什么不建议使用二级缓存. ...
- 三十一 Python分布式爬虫打造搜索引擎Scrapy精讲—chrome谷歌浏览器无界面运行、scrapy-splash、splinter
1.chrome谷歌浏览器无界面运行 chrome谷歌浏览器无界面运行,主要运行在Linux系统,windows系统下不支持 chrome谷歌浏览器无界面运行需要一个模块,pyvirtualdispl ...
- go调度: 第二部分-go调度器
前言 这个博客是三部分中提供go调度器的语义和机制的部分. 博客三部分的顺序: 1) go调度: 第一部分-操作系统调度 2) go调度: 第二部分-go调度器 3) go调度: 第三部分-并发 介绍 ...
- Linux调度器 - deadline调度器
一.概述 实时系统是这样的一种计算系统:当事件发生后,它必须在确定的时间范围内做出响应.在实时系统中,产生正确的结果不仅依赖于系统正确的逻辑动作,而且依赖于逻辑动作的时序.换句话说,当系统收到某个请求 ...
- goroutine与调度器
29 November 2013 by skoo 我们都知道Go语言是原生支持语言级并发的,这个并发的最小逻辑单元就是goroutine.goroutine就是Go语言提供的一种用户态线程,当然这种用 ...
- linux调度器源码分析 - 概述(一)
本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 引言 调度器作为操作系统的核心部件,具有非常重要的意义,其随着linux内核的更新也不断进行着更新.本系列文章通 ...
- FreeRTOS 任务与调度器(1)
前言: Task.c和Task.h文件内是FreeRTOS的核心内容,所有任务和调度器相关的API函数都在这个文件中,它包括下图这些内容FreeRTOS文件如下: Task.c和Task.h文件内是F ...
随机推荐
- 题解:P10672 【MX-S1-T1】壁垒
暑期集训=依托答辩. 分析 种类数是奇数一定无解. 否则每种数字先输出一次,在此过程中每增加两个数时,因为每个数字种类数都不一样,所以前缀种类数也同时增加 \(2\),保证一定为偶数. 然后输出完以后 ...
- 靶机练习: hacksudo---Thor
靶机:hacksudo---Thor 准备工作 靶机地址: http://download.vulnhub.com/hacksudo/hacksudo---Thor.zip MD5 校验:d12168 ...
- 18B20的CRC8校验分析
CRC即循环冗余校验码(Cyclic Redundancy Check):是数据通信领域中最常用的一种差错校验码,其特征是信息字段和校验字段的长度可以任意选定. CRC校验可以简单地描述为:例如我们要 ...
- Jmeter函数助手30-groovy
groovy函数用于脚本执行. 表达式评估:填入Apache Groovy脚本(不是文件名).本身包含逗号的参数值应根据需要进行转义'\,' 存储结果的变量名(可选) 1.引用变量进行截取字符处理 $ ...
- 【Vue2】Vue-Cli使用
1.需要NodeJS环境支持,此处省略NodeJS安装 2.使用NPM命令安装CLI包 vue-cli是npm.上的一个全局包,使用npm install 命令,即可方便的把它安装到自己的电脑上: n ...
- 【IDEA】创建Maven工程
当前工程,点new - project 选Maven,不需要点选什么骨架创建,骨架创建要下载大量依赖,生成时间太长, 空Maven的目的是让我们自己了解这个项目结构,需要什么依赖再加什么依赖 框线内的 ...
- pve 安装配置问题集锦
官网:https://www.proxmox.com/en/ 下载:https://www.proxmox.com/en/downloads 安装:https://pve.proxmox.com/wi ...
- 并行化强化学习 —— 初探 —— 并行reinforce算法的尝试 (中篇:强化学习在大规模仿真环境下单步交互并行化设计的可行性)
本篇博客是前篇博客并行化强化学习 -- 初探 -- 并行reinforce算法的尝试 (上篇:强化学习在多仿真环境下单步交互并行化设计的可行性)的继续,文中代码地址为:https://gitee.co ...
- 强化学习、分布式计算方向的phd毕业后去企业的要求
实验室慕师弟马上要phd毕业了,虽然我是遥遥无期,但是看到身边同学可以上岸还是提师弟高兴.由于师弟准备去企业工作,于是乎我也不免好奇起来phd毕业后去公司会有什么样的要求,于是网上找了找招聘信息,挑了 ...
- Windows 修改本地hosts文件
在在使用win下面的一些php集成开发工具的时候(比如 phpstudy wampserver等) 有时候会有这样的需求:我不想通过localhost/xxx/xxx/xxx.php 这样的方式访问我 ...