Goroutine 一泄露就看到他,這是個什麼?
大家好,我是煎魚。
作爲一個 Go 語言的使用大戶,常常就有人冷不丁的,一下就泄露了... 泄露了啥。煎魚幫我看看?
表象來看當然是 goroutine 泄露了,這時候就會有小夥伴開始跑去拉取 PProf。就會看到類似下面這張圖:
重點會看到 runtime.gopark
這個函數,在所有的 goroutine 泄露中都會看到有,並且都會是大頭。
既然是大頭,也就會有許多朋友以爲他是泄漏點,在那一頓猛查,那這個函數到底是什麼,作用是?
runtime.gopark 是何物
想要知道 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
函數: -
獲取當前 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 種,有興趣的小夥伴可以瞭解。如下:
總結
在今天這篇文章中,我們介紹了大家最常碰到的 goroutine 泄露,而在泄露後最關心的 runtime.gopark
函數的意義,我們從源碼再到作用進行了一輪剖析。
下次如果再有人問你 runtime.gopark
是幹嘛用的,就可以愉快的把這篇文章甩給他,分享你的知識啦 :)
關注煎魚,吸取他的知識 👆
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/W4dmhdjbiRH0KWSDdpXikQ