前言
用golang已經有一段時間了,中間用到了定時器,也在實踐了不少go經典的for+select模型,一直不太明白,正好最近有一個自己挖的坑——服務cpu佔用率極高。初步定位了一下應該是由於定時器的使用不當導致的cpu佔用率居高不下的情況。這邊簡單記錄一下golang定時器的一些使用陷阱和正確的姿勢,以需求的方式描述並分析記錄。
需求一:實現定時打印一句話
需求二:定時打印一句話,並同時累計一個數值
一、需求一實現
golang中提供了兩種“定時器”,ticker
和timer
,兩種雖然都能實現定時的功能,但是詳細上是有區別的,簡單說:
timer
是一個一次性的鬧鐘,時間到了執行完操作就不再執行(手動重置可以繼續執行)
ticker
是一個定時鬧鐘,每到設定的時間就執行,一直會執行下去
既然這樣的話我們分別使用兩種定時器來實現這個功能,詳細的代碼
1 timer實現
1.1 錯誤的實現方式
先說結論,本代碼實現中,當間隔的時間比較小時,比如5ms間隔,會發生內存泄露的問題,demo的內存一直持續增高。
func timer1(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Printf("work 退出")
return
case <-time.After(time.Millisecond * 5):
fmt.Printf("%v 是時候了\n", time.Now().Format(time.ANSIC))
}
}
}
1.2 正確的實現方式
func timer2(ctx context.Context) {
fmt.Printf("%v 現在還不是時候\n", time.Now().Format(time.ANSIC))
t := time.NewTimer(time.Second * 5)
defer t.Stop()
for {
select {
case <-ctx.Done():
fmt.Printf("work 退出")
return
case <-t.C:
//fmt.Printf("%v 是時候了\n", time.Now().Format(time.ANSIC))
t.Reset(time.Millisecond * 5)
}
}
}
1.3 錯誤分析
1.1中代碼case <-time.After(time.Millisecond * 5):
部分是存在內存泄露的關鍵,我們查看一下源碼:
每個After函數都會New一個新的對象,時間間隔太小時,新創建的對象未被釋放掉,導致內存泄露。
2 ticker實現
2.1 錯誤的實現方式
先說結論,本代碼實現中,當間隔的時間比較小時,比如5ms間隔,會發生內存泄露的問題,demo的內存一直持續增高。
func timer3(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Printf("work 退出")
return
case <-time.Tick(time.Millisecond * 5):
fmt.Printf("%v 是時候了\n", time.Now().Format(time.ANSIC))
}
}
}
2.2 正確的實現方式
func timer4(ctx context.Context) {
fmt.Printf("%v 現在還不是時候\n", time.Now().Format(time.ANSIC))
t := time.NewTicker(time.Millisecond * 5)
defer t.Stop()
for {
select {
case <-ctx.Done():
fmt.Printf("work 退出")
return
case <-t.C:
//fmt.Printf("%v 是時候了\n", time.Now().Format(time.ANSIC))
}
}
}
2.3 錯誤分析
2.1中代碼case <-time.Tick(time.Millisecond * 5):
同樣是導致內存泄露的主要原因,源碼:
二、需求二
任務分解之後是兩個功能,定時打印,累計數據。定時打印經過上面我們知道了怎麼去正確實現,剩下就是累計的功能了。
1 錯誤實現
1.1 實現1
先說結論:能累計,不會定時執行,但CPU佔用率高
func work1(ctx context.Context) {
c := 0
for {
select {
case <-ctx.Done():
fmt.Printf("work 退出")
return
case <-time.After(time.Second * 5):
fmt.Printf("%v 差不多行了\n", time.Now().Format(time.ANSIC))
default:
c++
}
}
}
1.2 實現2
先說結論:能累計,會定時執行,但CPU佔用率高
func work2(ctx context.Context) {
c := 0
tick := time.NewTicker(time.Second * 5)
defer tick.Stop()
for {
select {
case <-ctx.Done():
fmt.Printf("work 退出")
return
case <-tick.C:
fmt.Printf("%v 差不多行了\n", time.Now().Format(time.ANSIC))
default:
c++
}
}
}
2 正確實現
2.1 實現1
func work3(ctx context.Context) {
c := 0
tick := time.NewTicker(time.Second * 5)
defer tick.Stop()
for {
select {
case <-ctx.Done():
fmt.Printf("work 退出")
return
case <-tick.C:
fmt.Printf("%v 差不多行了\n", time.Now().Format(time.ANSIC))
default:
c++
time.Sleep(time.Nanosecond * 1)
}
}
}
2.2 實現2
func work4(ctx context.Context) {
c := 0
timer := time.NewTimer(time.Second * 5)
defer timer.Stop()
for {
select {
case <-ctx.Done():
fmt.Printf("work 退出")
return
case <-timer.C:
fmt.Printf("%v 差不多行了\n", time.Now().Format(time.ANSIC))
timer.Reset(time.Second * 5)
default:
c++
time.Sleep(time.Nanosecond * 1)
}
}
}
3 結果分析
對比上述1和2的實現,cpu高是因爲在default中沒有sleep,每到default會佔用cpu不會釋放,導致CPU很高。