Goroutine 一泄露就看到它,这个是什么?

polarisxu

共 2485字,需浏览 5分钟

 ·

2021-09-01 09:07

大家好,我是煎鱼。

作为一个 Go 语言的使用大户,常常就有人冷不丁的,一下就泄露了...泄露了啥。煎鱼帮我看看?

表象来看当然是 goroutine 泄露了,这时候就会有小伙伴开始跑去拉取 PProf。就会看到类似下面这张图:

重点会看到 runtime.gopark 这个函数,在所有的 goroutine 泄露中都会看到有,并且都会是大头。

既然是大头,也就会有许多朋友以为他是泄漏点,在那一顿猛查,那这个函数到底是什么,作用是?

runtime.gopark 是何物

想要知道 runtime.gopark 函数是作用,最快的办法就是看源码了。其实现细节在 src/runtime/proc.go 文件中。

源代码如下:

func gopark(unlockf func(*g, unsafe.Pointer) boollock unsafe.Pointerreason waitReasontraceEv bytetraceskip 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 函数:
    • 获取当前 goroutine 所绑定的 m,设置各类所需数据。
    • 调用 releasem 函数将当前 goroutine 和其 m 的绑定关系解除
  • 调用 park_m 函数:
    • 将当前 goroutine 的状态从 _Grunning 切换为 _Gwaiting,也就是等待状态。
    • 删除 m 和当前 goroutine m->curg(简称gp)之间的关联。
  • 调用 mcall 函数,仅会在需要进行 goroutiine 切换时会被调用:
    • 切换当前线程的堆栈,从 g 的堆栈切换到 g0 的堆栈并调用 fn(g) 函数。
    • 将 g 的当前 PC/SP 保存在 g->sched 中,以便后续调用 goready 函数时可以恢复运行现场。

熟读了其源码后,我们可得知该函数的关键作用就是将当前的 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 面试、工作经验、架构设计,加微信拉读者交流群,记得点赞!

浏览 426
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报