最近在工作上進展蠻順利的,在技術上有了更深一層的體會
雖然主題要說的這兩個東西其實蠻簡單的,不過直到最近才在實戰上遇到,才有了更深層的體會
還有想說抱著打聽消息的心態去面試就被問到了
沒有事先準備,憑著在公司的經驗憑空作畫的寫出來,好險用了3年的 Go 沒有白費
剛使用 goroutine 的時候對產生 leak 的場景其實是很陌生的
想著的是我的程式都照著預期的執行會有什麼問題?
不過有這個想法的下一秒我就想到,萬一出了問題怎麼辦?
今天當程式碼在操作的時候,會遇到幾種情形
接著就可以開始探討為什麼會出現 leak 的狀況
狀況是情境1,那當然沒什麼問題,例如你只需要 a+b 之後做回傳
那如果是2、3、4呢?goroutine 也是正在運行的協程(coroutine)
需要等待某個部分處理完畢才能夠繼續運行下去,此時在等待處理的資源如果永遠都處理不完
那這個 goroutine 就永遠不會處理完並且不會釋放資源,造成 leak
啟動 http server
然後做出一隻 API 請求另一個伺服器,並且永遠不會回傳也沒有 timeout,就會發生這個場景
channel block 又是怎麼回事?看看 graceful shutdown 就知道了
func main() {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
log.Println("Go main process working")
v := <-c
log.Println(v)
}
平常會利用 graceful shutdown 來監聽閉關事件
所以會有個 channel block 住以防止 main process 直接運行到底直接結束
那反過來說,要是在使用 goroutine 的時候 channel block,那就永遠卡住了
package main
import (
"github.com/gin-gonic/gin"
"log"
"runtime"
"time"
)
var totalClick int
func main() {
go checkGoroutine()
r := gin.Default()
r.GET("myTestPath", goroutineTest)
r.Run(":8000")
}
func checkGoroutine() {
for {
time.Sleep(1 * time.Second)
log.Printf("total leaked goroutines:%v", runtime.NumGoroutine())
}
}
func goroutineTest(ctx *gin.Context) {
log.Println("got a http request")
// sender
ch := make(chan int)
go sendNum(ch) // leak
// receiver, you can mark or unmark bottom 1 lines to get goroutine obvious log changes
go releaseChan(ch, ctx) // try to mark this line
ctx.JSON(200, nil)
}
func sendNum(ch chan<- int) {
totalClick++
ch <- totalClick
log.Println("this line is not executed until ch is unblocked") // blocked here
}
func releaseChan(ch <-chan int, ctx *gin.Context) {
for {
select {
case <-ctx.Request.Context().Done():
log.Printf("unblocked chan that value is %v", <-ch)
return
}
}
}
從發生情境我們可以看到有幾種會發生的問題點,總結起來防範的方法是有規律的
etcd
uber-go
這兩個 package 實作檢查 goroutine leak 的方法runtime.NumGoroutine()
粗略觀察