曹大带我学 Go(5)—— 哪里来的 goexit
你好,我是小X。
曹大最近开 Go 课程了,小X 正在和曹大学 Go。
这个系列会讲一些从课程中学到的让人醍醐灌顶的东西,拨云见日,带你重新认识 Go。
在学员群里,有同学在用 dlv 调试时看到了令人不解的 goexit
:goexit 函数是啥,为啥 go fun(){}()
的上层是它?看着像是一个“退出”函数,为什么会出现在最上层?
其实如果看过 pprof 的火焰图,也会经常看到 goexit
这个函数。
我们来个例子重现一下:
package mainimport "time"func main() {go func () {println("hello world")}()time.Sleep(10*time.Minute)
}
启动 dlv 调试,并分别在不同的地方打上断点:
(dlv) b a.go:5
Breakpoint 1 (enabled) set at 0x106d12f for main.main() ./a.go:5
(dlv) b a.go:6
Breakpoint 2 (enabled) set at 0x106d13d for main.main() ./a.go:6
(dlv) b a.go:7
Breakpoint 3 (enabled) set at 0x106d1a0 for main.main.func1() ./a.go:7
执行命令 c
运行到断点处,再执行 bt
命令得到 main 函数的调用栈:
(dlv) bt
0 0x000000000106d12f in main.mainat ./a.go:5
1 0x0000000001035c0f in runtime.mainat /usr/local/go/src/runtime/proc.go:204
2 0x0000000001064961 in runtime.goexitat /usr/local/go/src/runtime/asm_amd64.s:1374
它的上一层是 runtime.main
,找到原代码位置,位于 src/runtime/proc.go
里的 main
函数,它是 Go 进程的 main goroutine,这里会执行一些 init 操作、开启 GC、执行用户 main 函数……
fn := main_main // proc.go:203
fn() // proc.go:204
其中 fn
是 main_main
函数,表示用户的 main 函数,执行到了这里,才真正将权力交给用户。
继续执行 c
命令和 bt
命令,得到 go
这一行的调用栈:
0 0x000000000106d13d in main.mainat ./a.go:6
1 0x0000000001035c0f in runtime.mainat /usr/local/go/src/runtime/proc.go:204
2 0x0000000001064961 in runtime.goexitat /usr/local/go/src/runtime/asm_amd64.s:1374
以及 println
这一句的调用栈:
0 0x000000000106d1a0 in main.main.func1at ./a.go:7
1 0x0000000001064961 in runtime.goexitat /usr/local/go/src/runtime/asm_amd64.s:1374
可以看到,调用栈的最上层都是 runtime.goexit
,我们跟着注明了的代码行数,顺藤摸瓜,找到 goexit 代码:
// The top-most function running on a goroutine
// returns to goexit+PCQuantum.
TEXT runtime·goexit(SB),NOSPLIT,$0-0BYTE $0x90 // NOPCALL runtime·goexit1(SB) // does not return// traceback from goexit1 must hit code range of goexitBYTE $0x90 // NOP
这还是个汇编函数,它接着调用 goexit1 函数、goexit0 函数,主要的功能就是将 goroutine 的各个字段清零,放入 gFree 队列里,等待将来进行复用。
另一方面,goexit 函数的地址是在创建 goroutine 的过程中,塞到栈上的。让 CPU “误以为”:func()
是由 goexit 函数调用的。这样一来,当 func()
执行完毕时,会返回到 goexit 函数做一些清理工作。
下面这张图能看出在 newg 的栈底塞了一个 goexit 函数的地址:
对应的路径是:
newporc -> newporc1 -> gostartcallfn -> gostartcall
来看 newproc1
中的关键几行代码:
newg.sched.pc = funcPC(goexit) + sys.PCQuantum
newg.sched.g = guintptr(unsafe.Pointer(newg))
gostartcallfn(&newg.sched, fn)
这里的 newg 就是创建的 goroutine,每个新建的 goroutine 都会执行这些代码。而 sched
结构体其实保存的是 goroutine 的执行现场,每当 goroutine 被调离 CPU,它的执行进度就是保存到这里。进度主要就是 SP、BP、PC,分别表示栈顶地址、栈底地址、指令位置,等 goroutine 再次得到 CPU 的执行权时,会把 SP、BP、PC 加载到寄存器中,从而从断点处恢复运行。
回到上面的几行代码,pc
被赋值成了 funcPC(goexit)
,最后在 gostartcall
里:
// adjust Gobuf as if it executed a call to fn with context ctxt
// and then did an immediate gosave.
func gostartcall(buf *gobuf, fn, ctxt unsafe.Pointer) {sp := buf.sp...sp -= sys.PtrSize*(*uintptr)(unsafe.Pointer(sp)) = buf.pcbuf.sp = spbuf.pc = uintptr(fn)buf.ctxt = ctxt
}
sp
其实就是栈顶,第 7 行代码把 buf.pc
,也就是 goexit
的地址,放在了栈顶的地方,熟悉 Go 函数调用规约的朋友知道,这个位置其实就是 return addr
,将来等 func()
执行完,就会回到父函数继续执行,这里的父函数其实就是 goexit
。
一切早已注定。
不过注意一点,main goroutine
和普通的 goroutine 不同的是,前者执行完用户 main 函数后,会直接执行 exit 调用,整个进程退出:
也就不会进入 goexit 函数。而普通 goroutine 执行完毕后,则直接进入 goexit 函数,做一些清理工作。
这也就是为什么只要 main goroutine 执行完了,就不会等其他 goroutine,直接退出。一切都是因为 exit
这个调用。
今天我们主要讲了 goexit 是怎么被安插到 goroutine 的栈上,从而实现 goroutine 执行完毕后再回到 goexit 函数。
原来看似很不理解的东西,是不是更清晰了?
源码面前,了无秘密。
好了,这就是今天全部的内容了~ 我是小X,我们下期再见~
欢迎关注曹大的 TechPaper 以及码农桃花源~
曹大带我学 Go(5)—— 哪里来的 goexit相关推荐
- 曹大带我学 Go(8)—— 一个打点引发的事故
你好,我是小X. 曹大最近开 Go 课程了,小X 正在和曹大学 Go. 这个系列会讲一些从课程中学到的让人醍醐灌顶的东西,拨云见日,带你重新认识 Go. 最近线上事故频发,搞得焦头烂额,但是能用上跟曹 ...
- 曹大带我学 Go(6)—— 技术之外
你好,我是小X. 曹大最近开 Go 课程了,小X 正在和曹大学 Go. 这个系列会讲一些从课程中学到的让人醍醐灌顶的东西,拨云见日,带你重新认识 Go. 有学员私下和我说,这个课程挺打击他的自信心.我 ...
- 曹大带我学 Go(2)—— 迷惑的 goroutine 执行顺序
你好,我是小X. 曹大最近开 Go 课程了,小X 正在和曹大学 Go. 这个系列会讲一些从课程中学到的让人醍醐灌顶的东西,拨云见日,带你重新认识 Go. 上一篇文章我们讲了 Go 调度的本质是一个生产 ...
- 『曹大带我学 Go 』系列文章汇总
你好,我是小 X. 之前写了 11 篇跟着曹大学 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. 最近一个年久失修的库导致了线上事故,不得不去做 ...
最新文章
- 利用swapoff和swapon刷新swap缓存
- windos10下编译opencv_4.0.1+opencv-contrib_4.0.1
- 入坑 Electron 开发跨平台桌面应用
- 如何将h5网页改成微信网页
- linux认证哪家好,linux认证 考哪种好?
- 云服务器 ECS 搭建WordPress网站:备案
- 天文坐标系的转换 时角坐标和赤道坐标系的转化
- CMMI认证要求有哪些
- 【R语言】如何直接调取Wind、iFinD数据接口教程
- 2.1微信小程序简介
- 微信小程序中wx.canIUse的坑
- 2021年美妆护肤行业电商营销报告
- QT .pro文件详解
- ffmpeg截取一段视频
- win10浏览器不能联网,电脑上其他软件可以联网
- 高性能永磁交流伺服电机系统控制策略
- 云豹php短视频源码实现身份证验证的方法
- MyBatis的基础查询
- Windows下如何查看十几G的日志文件
- 小小滑块可笑可笑-安卓滑块验证码通杀方案研究(5)