iT邦幫忙

0

Golang - goroutine leak - 1

  • 分享至 

  • xImage
  •  

最近在工作上進展蠻順利的,在技術上有了更深一層的體會
雖然主題要說的這兩個東西其實蠻簡單的,不過直到最近才在實戰上遇到,才有了更深層的體會
還有想說抱著打聽消息的心態去面試就被問到了
沒有事先準備,憑著在公司的經驗憑空作畫的寫出來,好險用了3年的 Go 沒有白費

前言

剛使用 goroutine 的時候對產生 leak 的場景其實是很陌生的
想著的是我的程式都照著預期的執行會有什麼問題?
不過有這個想法的下一秒我就想到,萬一出了問題怎麼辦?

章節

  1. Goroutine leak 的情境加上案例
  2. 防範 goroutine leak
  3. 利用 code 發現 goroutine leak
  4. 利用 pprof 發現 goroutine leak(下篇待續)

Goroutine leak

今天當程式碼在操作的時候,會遇到幾種情形

  1. 處理只針對自身 server,並且耦合度很低於其他部分沒什麼關連
  2. 對 database 的請求
  3. 對別的伺服器的請求(http request)
  4. channel block

接著就可以開始探討為什麼會出現 leak 的狀況
狀況是情境1,那當然沒什麼問題,例如你只需要 a+b 之後做回傳
那如果是2、3、4呢?goroutine 也是正在運行的協程(coroutine)
需要等待某個部分處理完畢才能夠繼續運行下去,此時在等待處理的資源如果永遠都處理不完
那這個 goroutine 就永遠不會處理完並且不會釋放資源,造成 leak

Example 1

啟動 http server
然後做出一隻 API 請求另一個伺服器,並且永遠不會回傳也沒有 timeout,就會發生這個場景

Example 2

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,那就永遠卡住了

Example 3

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
		}
	}
}

防範 Goroutine leak

從發生情境我們可以看到有幾種會發生的問題點,總結起來防範的方法是有規律的

防範機制

  1. 增加 timeout 或其他終止機制

觀察方法

  1. 自製監聽方法觀察 leak
  2. 透過 pprof 觀察
  3. 參考連結1內有介紹 etcd uber-go 這兩個 package 實作檢查 goroutine leak 的方法
    可以利用 https://github.com/uber-go/goleak 檢查 leak(拆包即用)
  4. 透過 runtime.NumGoroutine() 粗略觀察

參考

  1. https://evex.one/posts/go/goroutine-leak/
  2. https://hoverzheng.github.io/post/technology-blog/go/goroutine-leak%E5%92%8C%E8%A7%A3%E5%86%B3%E4%B9%8B%E9%81%93/
  3. https://github.com/uber-go/goleak
  4. https://doc.zkbhj.com/golang/8-goroutine-leak.html

圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言