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 ...
随机推荐
- 让摄像头带上智慧“智驭视界·AIEye”
接上一篇<物联网浏览器(IoTBrowser)-基于计算机视觉开发的应用"智慧眼AIEye">,经过AI的包装很高级,确实很屌炸天. 智驭视界·AIEye 在科技赋能的 ...
- [oeasy]python0015_键盘改造_将esc和capslock对调_hjkl_移动_双手正位
键盘改造 回忆上次内容 上次练习了复制粘贴 按键 作用 <kbd>y</kbd><kbd>y</kbd> 复制光标行代码 到剪贴板 <kbd> ...
- oeasy教您玩转vim - 3 - # 打开文件
打开文件 回忆上节课内容 什么不会,就 :help 什么 命令有完整和简写两种模式 :help 和 :h :quit 和 :q 注意:是左右手小拇指配合输入的 vim 有 6 种基本模式 但是我们还没 ...
- [rCore学习笔记 07]移除标准库依赖
改造Rust hello world 移除println!宏 rustc添加对裸机的支持 rustup target add riscv64gc-unknown-none-elf detail rus ...
- ComfyUI进阶:Comfyroll插件 (六)
ComfyUI进阶:Comfyroll插件 (六) 前言: 学习ComfyUI是一场持久战,而Comfyroll 是一款功能强大的自定义节点集合,专为 ComfyUI 用户打造,旨在提供更加丰富和专业 ...
- 映射lun
环境 VMware1 网卡与 主机ping通 创建文件夹将文件解压进去 删除压缩包 创建虚拟机 下一步下一步,完成虚拟机 编辑虚拟机设置 否 账号密码 admin Admin@storage 等待 导 ...
- Jmeter函数助手22-V
V函数用于执行变量名.嵌套函数.类似eval函数 Name of variable (may include variable and function references):必填,填入变量名称或者 ...
- 【C3】02 操作总览
在这篇文章中,我们将会拿一个简单的HTML文档做例子,并且在上边使用CSS样式,期待你能在此过程中学会更多有关CSS的实战性知识. 前置知识 在开始本单元之前,您应该: 基本熟悉计算机操作. 基本工作 ...
- mini_imagenet 数据集生成工具
最近在看小样本方面的论文,发现这个mini_imagenet这个数据集比较常用,但是却不好找,找了半天也没有找到,最后在找到了这样的答案: 小样本学习(Few shot learning)标准数据集( ...
- 关于我升级VS16.8,结果一些项目运行报错“Phx.FatalError”这件事
背景 不知道啥时候开始,一些的项目不能好好运行了.一运行就报错 解决办法 https://developercommunity.visualstudio.com/content/problem/125 ...