大家好,我是煎鱼。
作为一个 Go 语言的使用大户,常常就有人冷不丁的,一下就泄露了...泄露了啥。煎鱼帮我看看?
表象来看当然是 goroutine 泄露了,这时候就会有小伙伴开始跑去拉取 PProf。就会看到类似下面这张图:
重点会看到 runtime.gopark
这个函数,在所有的 goroutine 泄露中都会看到有,并且都会是大头。
既然是大头,也就会有许多朋友以为他是泄漏点,在那一顿猛查,那这个函数到底是什么,作用是?
想要知道 runtime.gopark
函数是作用,最快的办法就是看源码了。其实现细节在 src/runtime/proc.go 文件中。
源代码如下:
func gopark(unlockf func(*g, unsafe.Pointer) bool, lock unsafe.Pointer, reason waitReason, traceEv byte, traceskip int) {
mp := acquirem()
gp := mp.curg
status := readgstatus(gp)
mp.waitlock = lock
mp.waitunlockf = unlockf
gp.waitreason = reason
mp.waittraceev = traceEv
mp.waittraceskip = traceskip
releasem(mp)
mcall(park_m)
}
该函数主要作用有三大点:
acquirem
函数:releasem
函数将当前 goroutine 和其 m 的绑定关系解除。park_m
函数:_Grunning
切换为 _Gwaiting
,也就是等待状态。mcall
函数,仅会在需要进行 goroutiine 切换时会被调用:熟读了其源码后,我们可得知该函数的关键作用就是将当前的 goroutine 放入等待状态,这意味着 goroutine 被暂时被搁置了,也就是被运行时调度器暂停了。
回到最初的问题,之所以 goroutine 泄露,你就会看到大量的 runtime.gopark
函数,这是因为 goroutine 泄露一般不会单单只是一个 goroutine,肯定是会有多个的。
同时这些 goroutine 在调用了 runtime.gopark
函数后都被暂停了,也就是进入休眠状态,自然而然也就停留在此。
直至满足条件后再被 runtime.goready
函数唤醒,该函数会将已准备就绪的 goroutine 切换状态,再加入运行队列,等待调度器的新一轮调度。
前几天就有读者在我的 Go 读者群(可以加我后拉你进群)中咨询了下述问题,也和 runtime.gopark
函数有关。问题如下:
经过上述的分析,显然 runtime.gopark
不是 goroutine 的一种状态,导致 goroutine 状态变更只是他的执行过程中所涉及到,产生的一个结果。
而 goroutine 的状态一共有 9 种,有兴趣的小伙伴可以了解。如下:
状态 | 含义 |
---|---|
_Gidle | 刚刚被分配,还没有进行初始化。 |
_Grunnable | 已经在运行队列中,还没有执行用户代码。 |
_Grunning | 不在运行队列里中,已经可以执行用户代码,此时已经分配了 M 和 P。 |
_Gsyscall | 正在执行系统调用,此时分配了 M。 |
_Gwaiting | 在运行时被阻止,没有执行用户代码,也不在运行队列中,此时它正在某处阻塞等待中。 |
_Gmoribund_unused | 尚未使用,但是在 gdb 中进行了硬编码。 |
_Gdead | 尚未使用,这个状态可能是刚退出或是刚被初始化,此时它并没有执行用户代码,有可能有也有可能没有分配堆栈。 |
_Genqueue_unused | 尚未使用。 |
_Gcopystack | 正在复制堆栈,并没有执行用户代码,也不在运行队列中。 |
在今天这篇文章中,我们介绍了大家最常碰到的 goroutine 泄露,而在泄露后最关心的 runtime.gopark
函数的意义,我们从源码再到作用进行了一轮剖析。
下次如果再有人问你 runtime.gopark
是干嘛用的,就可以愉快的把这篇文章甩给他,分享你的知识啦 :)
关注煎鱼,吸取他的知识 👆
你好,我是煎鱼。高一折腾过前端,参加过国赛拿了奖,大学搞过 PHP。现在整 Go,在公司负责微服务架构等相关工作推进和研发。
从大学开始靠自己赚生活费和学费,到出版 Go 畅销书《Go 语言编程之旅》,再到获得 GOP(Go 领域最有观点专家)荣誉,点击蓝字查看我的出书之路。
日常分享高质量文章,输出 Go 面试、工作经验、架构设计,加微信拉读者交流群,记得点赞!