Go語言中Timer計時器的使用技巧詳解
time包里有個Timer計時器的功能,主要的結構和函數有:
type Timer struct { C <-chan Time r runtimeTimer } func After(d Duration) <-chan Time func AfterFunc(d Duration, f func()) *Timer func NewTimer(d Duration) *Timer func (*Timer) Reset(d Duration) bool func (*Timer) Stop() bool
三個基本用法:
c := time.After(time.Second) fmt.Println(<-c) t := time.NewTimer(time.Second) fmt.Println(<-t.C) tc := make(chan int) time.AfterFunc(time.Second, func() { tc <- 1 }) fmt.Println(<-tc)
After
函數實際就是return NewTimer(d).C
,和NewTimer
的用法類似,但Timer
本身還有Reset
、Stop
等方法可用,有相關需求的,應使用NewTimer
。
AfterFunc
相當于在d Duration
之后創(chuàng)建了一個執(zhí)行f
的goroutine,返回的Timer
本身并不會阻塞,也不能像前面的例子那樣使用Timer.C
,但可以使用Reset
、Stop
等方法。
導致上面區(qū)別的原因在于使用NewTimer
和AfterFunc
生成計時器的時候,內部使用的調用參數并不相同。
NewTimer:
func NewTimer(d Duration) *Timer { c := make(chan Time, 1) t := &Timer{ C: c, r: runtimeTimer{ when: when(d), f: sendTime, arg: c, }, } startTimer(&t.r) return t } func sendTime(c interface{}, seq uintptr) { // Non-blocking send of time on c. // Used in NewTimer, it cannot block anyway (buffer). // Used in NewTicker, dropping sends on the floor is // the desired behavior when the reader gets behind, // because the sends are periodic. select { case c.(chan Time) <- Now(): default: } }
NewTimer
在計時器完成時使用sendTime
函數,非阻塞的向Timer.C
中傳入當前時間,所以在計時器完成時,可以從其中獲取內容。
AfterFunc:
func AfterFunc(d Duration, f func()) *Timer { t := &Timer{ r: runtimeTimer{ when: when(d), f: goFunc, arg: f, }, } startTimer(&t.r) return t } func goFunc(arg interface{}, seq uintptr) { go arg.(func())() }
AfterFunc
則是在計時器完成時調用goFunc
,在goFunc
中啟動一個執(zhí)行參數f
的goroutine,而并未對Timer.C
進行任何操作,于是我們無法從其中獲取內容。
注:下面的內容主要基于NewTimer
創(chuàng)建的Timer
Timer
使用的關鍵點:
一,在一些任務中我們需要多次重復計時,不要使用循環(huán)創(chuàng)建大量計時器,會影響性能,盡量使用Reset
和Stop
來復用已創(chuàng)建的計時器。
二,Timer
的Stop
方法并不會關閉Timer.C
,可能會導致意外的阻塞,如:
func main() { timer := time.NewTimer(time.Second) go func() { timer.Stop() }() <-timer.C }
會導致程序阻塞,無法退出。
關于Timer
的Reset
和Stop
的使用小技巧:
// 用下面的非阻塞方法使用Stop func timerStop(t *time.Timer) { if !t.Stop() { select { case <-t.C: default: } } } // Reset之前先執(zhí)行Stop func timerReset(t *time.Timer, d time.Duration) { timerStop(t) t.Reset(d) }
關于Reset
之前為何要Stop
,time
包的Reset
文檔如下說:
For a Timer created with NewTimer, Reset should be invoked only on stopped or expired timers with drained channels.
對于使用NewTimer創(chuàng)建的Timer,Reset應該用在已經停止或過期,并已經排空管道的計時器上。
If a program has already received a value from t.C, the timer is known to have expired and the channel drained, so t.Reset can be used directly. If a program has not yet received a value from t.C, however, the timer must be stopped and—if Stop reports that the timer expired before being stopped—the channel explicitly drained:
如果一個程序已經從t.C中接收了值,計時器過期了并且管道已被排空,Reset可以直接使用。但如果程序還未從t.C中接收值,而計時器需要被停止,并且Stop方法報告計時器在被停止前已經過期,則管道需要被顯式的排空:
if !t.Stop() { <-t.C } t.Reset(d)
This should not be done concurrent to other receives from the Timer's channel.
這個操作不應與其他程序接收計時器的管道同時發(fā)生。
注意,上面的內容其實還沒表述完全。
如果我們需要停止一個計時器,并且計時器的Stop
方法報告為false
時,計時器的狀態(tài),以及t.C
的狀態(tài),共有三種可能:
- Stop前已經被Stop,t.C為空
- Stop前已經過期,計時器向t.C中寫入內容,t.C為滿
- Stop前已經過期,計時器向t.C中寫入內容,t.C的信息已被其他程序接收,t.C為空
前面文檔中的程序,僅在第2種情況會按照預期運行。
其他兩種情況,顯式排空<-t.C
的時候會阻塞。
就是因為上面的情況,才演化出前面的timerStop
函數。
但同時應該明白,timerStop
函數對應上面幾種情況時如何處理:
- select走default分支,跳過阻塞,但應考慮到計時器并不是當前Stop停止的
- select進行顯式排空,但應考慮到計時器并未被成功停止,并且t.C的內容被拋棄了
- select走default分支,跳過阻塞,但應考慮到計時器并未被成功停止,并且t.C的內容被其他程序利用了
充分的考慮到上面這幾點,就可以使用timerStop
函數了。
否則,應該充分考慮自己程序的需求,進行必要的修改。
到此這篇關于Go語言中Timer計時器的使用技巧詳解的文章就介紹到這了,更多相關Go Timer計時器內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
intelliJ?idea安裝go開發(fā)環(huán)境并搭建go項目(打包)全過程
最近在配置idea開發(fā)go語言時碰到很多問題,所以這里給大家總結下,這篇文章主要給大家介紹了關于intelliJ?idea安裝go開發(fā)環(huán)境并搭建go項目(打包)的相關資料,需要的朋友可以參考下2023-10-10golang中import cycle not allowed解決的一種思路
這篇文章主要給大家介紹了關于golang中import cycle not allowed解決的一種思路,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考借鑒,下面隨著小編來一起學習學習吧2018-08-08Qt6.5 grpc組件使用 + golang grpc server
這篇文章主要介紹了Qt6.5 grpc組件使用+golang grpc server示例,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-05-05Go語言利用time.After實現(xiàn)超時控制的方法詳解
最近在學習golang,所以下面這篇文章主要給大家介紹了關于Go語言利用time.After實現(xiàn)超時控制的相關資料,文中通過示例介紹的非常詳細,需要的朋友可以參考借鑒,下面隨著小編來一起學習學習吧2018-08-08CMD下執(zhí)行Go出現(xiàn)中文亂碼的解決方法
需要在Go寫的服務里面調用命令行或者批處理,并根據返回的結果做處理。但是windows下面用cmd返回中文會出現(xiàn)亂碼,本文就詳細的介紹一下解決方法,感興趣的可以了解一下2021-12-12