golang學習筆記-定時器相關

前言

用golang已經有一段時間了,中間用到了定時器,也在實踐了不少go經典的for+select模型,一直不太明白,正好最近有一個自己挖的坑——服務cpu佔用率極高。初步定位了一下應該是由於定時器的使用不當導致的cpu佔用率居高不下的情況。這邊簡單記錄一下golang定時器的一些使用陷阱和正確的姿勢,以需求的方式描述並分析記錄。
需求一:實現定時打印一句話
需求二:定時打印一句話,並同時累計一個數值

一、需求一實現

golang中提供了兩種“定時器”,tickertimer,兩種雖然都能實現定時的功能,但是詳細上是有區別的,簡單說:
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很高。
在這裏插入圖片描述

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