Go語言中Timer計(jì)時(shí)器的使用技巧詳解
time包里有個(gè)Timer計(jì)時(shí)器的功能,主要的結(jié)構(gòu)和函數(shù)有:
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三個(gè)基本用法:
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函數(shù)實(shí)際就是return NewTimer(d).C,和NewTimer的用法類似,但Timer本身還有Reset、Stop等方法可用,有相關(guān)需求的,應(yīng)使用NewTimer。
AfterFunc相當(dāng)于在d Duration之后創(chuàng)建了一個(gè)執(zhí)行f的goroutine,返回的Timer本身并不會(huì)阻塞,也不能像前面的例子那樣使用Timer.C,但可以使用Reset、Stop等方法。
導(dǎo)致上面區(qū)別的原因在于使用NewTimer和AfterFunc生成計(jì)時(shí)器的時(shí)候,內(nèi)部使用的調(diào)用參數(shù)并不相同。
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在計(jì)時(shí)器完成時(shí)使用sendTime函數(shù),非阻塞的向Timer.C中傳入當(dāng)前時(shí)間,所以在計(jì)時(shí)器完成時(shí),可以從其中獲取內(nèi)容。
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則是在計(jì)時(shí)器完成時(shí)調(diào)用goFunc,在goFunc中啟動(dòng)一個(gè)執(zhí)行參數(shù)f的goroutine,而并未對Timer.C進(jìn)行任何操作,于是我們無法從其中獲取內(nèi)容。
注:下面的內(nèi)容主要基于NewTimer創(chuàng)建的Timer
Timer使用的關(guān)鍵點(diǎn):
一,在一些任務(wù)中我們需要多次重復(fù)計(jì)時(shí),不要使用循環(huán)創(chuàng)建大量計(jì)時(shí)器,會(huì)影響性能,盡量使用Reset和Stop來復(fù)用已創(chuàng)建的計(jì)時(shí)器。
二,Timer的Stop方法并不會(huì)關(guān)閉Timer.C,可能會(huì)導(dǎo)致意外的阻塞,如:
func main() {
timer := time.NewTimer(time.Second)
go func() {
timer.Stop()
}()
<-timer.C
}會(huì)導(dǎo)致程序阻塞,無法退出。
關(guān)于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)
}關(guān)于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應(yīng)該用在已經(jīng)停止或過期,并已經(jīng)排空管道的計(jì)時(shí)器上。
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:
如果一個(gè)程序已經(jīng)從t.C中接收了值,計(jì)時(shí)器過期了并且管道已被排空,Reset可以直接使用。但如果程序還未從t.C中接收值,而計(jì)時(shí)器需要被停止,并且Stop方法報(bào)告計(jì)時(shí)器在被停止前已經(jīng)過期,則管道需要被顯式的排空:
if !t.Stop() {
<-t.C
}
t.Reset(d)This should not be done concurrent to other receives from the Timer's channel.
這個(gè)操作不應(yīng)與其他程序接收計(jì)時(shí)器的管道同時(shí)發(fā)生。
注意,上面的內(nèi)容其實(shí)還沒表述完全。
如果我們需要停止一個(gè)計(jì)時(shí)器,并且計(jì)時(shí)器的Stop方法報(bào)告為false時(shí),計(jì)時(shí)器的狀態(tài),以及t.C的狀態(tài),共有三種可能:
- Stop前已經(jīng)被Stop,t.C為空
- Stop前已經(jīng)過期,計(jì)時(shí)器向t.C中寫入內(nèi)容,t.C為滿
- Stop前已經(jīng)過期,計(jì)時(shí)器向t.C中寫入內(nèi)容,t.C的信息已被其他程序接收,t.C為空
前面文檔中的程序,僅在第2種情況會(huì)按照預(yù)期運(yùn)行。
其他兩種情況,顯式排空<-t.C的時(shí)候會(huì)阻塞。
就是因?yàn)樯厦娴那闆r,才演化出前面的timerStop函數(shù)。
但同時(shí)應(yīng)該明白,timerStop函數(shù)對應(yīng)上面幾種情況時(shí)如何處理:
- select走default分支,跳過阻塞,但應(yīng)考慮到計(jì)時(shí)器并不是當(dāng)前Stop停止的
- select進(jìn)行顯式排空,但應(yīng)考慮到計(jì)時(shí)器并未被成功停止,并且t.C的內(nèi)容被拋棄了
- select走default分支,跳過阻塞,但應(yīng)考慮到計(jì)時(shí)器并未被成功停止,并且t.C的內(nèi)容被其他程序利用了
充分的考慮到上面這幾點(diǎn),就可以使用timerStop函數(shù)了。
否則,應(yīng)該充分考慮自己程序的需求,進(jìn)行必要的修改。
到此這篇關(guān)于Go語言中Timer計(jì)時(shí)器的使用技巧詳解的文章就介紹到這了,更多相關(guān)Go Timer計(jì)時(shí)器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
intelliJ?idea安裝go開發(fā)環(huán)境并搭建go項(xiàng)目(打包)全過程
最近在配置idea開發(fā)go語言時(shí)碰到很多問題,所以這里給大家總結(jié)下,這篇文章主要給大家介紹了關(guān)于intelliJ?idea安裝go開發(fā)環(huán)境并搭建go項(xiàng)目(打包)的相關(guān)資料,需要的朋友可以參考下2023-10-10
golang中import cycle not allowed解決的一種思路
這篇文章主要給大家介紹了關(guān)于golang中import cycle not allowed解決的一種思路,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-08-08
Qt6.5 grpc組件使用 + golang grpc server
這篇文章主要介紹了Qt6.5 grpc組件使用+golang grpc server示例,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-05-05
Go語言利用time.After實(shí)現(xiàn)超時(shí)控制的方法詳解
最近在學(xué)習(xí)golang,所以下面這篇文章主要給大家介紹了關(guān)于Go語言利用time.After實(shí)現(xiàn)超時(shí)控制的相關(guān)資料,文中通過示例介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-08-08
CMD下執(zhí)行Go出現(xiàn)中文亂碼的解決方法
需要在Go寫的服務(wù)里面調(diào)用命令行或者批處理,并根據(jù)返回的結(jié)果做處理。但是windows下面用cmd返回中文會(huì)出現(xiàn)亂碼,本文就詳細(xì)的介紹一下解決方法,感興趣的可以了解一下2021-12-12
golang 實(shí)現(xiàn)Location跳轉(zhuǎn)方式
這篇文章主要介紹了golang 實(shí)現(xiàn)Location跳轉(zhuǎn)方式,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-05-05

