記一次Go程序CPU佔用100%的問題

我寫的一個服務,有段時間CPU佔用一直是100%。因爲一直在開發環境,還沒上線,今天終於有時間了就排查了一下。100%這種佔用,一看就感覺是某個for循環導致的。

首先,選擇分析工具,Golang的性能分析工具pprof。

在Gin這個框架中,需要通過註冊纔可以使用。
方式

if ok, _ := strconv.ParseBool(utils.GetEnv("DEVELOP_MODE")); ok {
		pprof.Register(router)
}

我們用了一個環境變量DEVELOP_MODE來控制,這樣子方便控制是否打開調試,因爲這個東西一旦開啓會對性能有影響。

如果不是用的gin框架,那麼直接導入包import _ "net/http/pprof" 就會打開調試,爲啥呢?因爲pprof/pprof.go文件是這麼寫的,只要程序啓動,導入後,該包就會被掃描到,然後就會默認加載以下幾項debug工具。

func init() {
	http.HandleFunc("/debug/pprof/", Index)
	http.HandleFunc("/debug/pprof/cmdline", Cmdline)
	http.HandleFunc("/debug/pprof/profile", Profile)
	http.HandleFunc("/debug/pprof/symbol", Symbol)
	http.HandleFunc("/debug/pprof/trace", Trace)
}

還有其他幾個沒有列出來,比如以下的幾項,這些是在Index函數里加載進來的。

profiles.m = map[string]*Profile{
			"goroutine":    goroutineProfile,
			"threadcreate": threadcreateProfile,
			"heap":         heapProfile,
			"allocs":       allocsProfile,
			"block":        blockProfile,
			"mutex":        mutexProfile,
		}

好了,廢話不多說。啓動性能調試,我們是CPU佔用,所以要使用profile模式來定位cpu佔用的問題,它會收集30s左右的運行數據。
命令行輸入以下命令:
go tool pprof http://xxxx:port/debug/pprof/profile

等待30s後會進入交互模式,支持的命令可以通過輸入help來查看。
在這裏插入圖片描述
這裏我們輸入top如下
在這裏插入圖片描述
top命令輸出5個字段,如下

  • flat 本函數的執行耗時
  • flat% flat佔 CPU 總時間的比例。程序總耗時 16.22s, Eat 的 16.19s 佔了 99.82%
  • sum% 前面每一行的 flat 佔比總和
  • cum 累計量。指該函數加上該函數調用的函數總耗時
  • cum% cum 佔 CPU 總時間的比例

如果命令看起來不夠裝逼,go還提供了可視化的工具。我們只要輸入web這個命令,就能得到一張svg圖,可通過瀏覽器打開,如下圖所示。
在這裏插入圖片描述
如果說top命令不夠直觀,那麼這張圖,就能看出來了吧,我們的程序20.19秒內一直在調用selectgo。看這張圖,是不是對於golang裏select channel的有了那麼一些瞭解了。看起來select耗時裏最多的還是加解鎖selunlocksellock,然後就是hchan的排序sortkey。

扯遠了,有時間要好好研究一下golang的select實現。回到問題本身,調用selectgo的是我們的程序裏一個叫handleXXEvent的for select語句。這個語句我咋寫的呢,類似如下的代碼

for {
	select {
	case x1 <- sss:
	// go do something
	case x2 <- ss:
	// go do something
	default:
	
}

我們知道select具有輪詢多個channel的功能,如果有事件進來,就走,否則就block住。但是有default的話,沒有事件就會走default。外面有一個for循環,所以就造成了cpu滿載空轉的問題。刪除default就好了,select就會block在那裏,等待io。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章