Go中的Timer 和 Ticker詳解
一:簡介
在日常開發(fā)中,我們可能會遇到需要延遲執(zhí)行或周期性地執(zhí)行一些任務。這個時候就需要用到 Go
語言中的定時器。
在 Go
語言中,定時器類型有兩種:一次性定時器time.Timer
和 周期性定時器time.Ticker
。本文將會對這兩種定時器類型進行介紹。
二、Timer:一次性定時器
Timer
是一個一次性的定時器,用于在未來的某一時刻執(zhí)行一次操作。
基本使用
創(chuàng)建 Timer
定時器的方式有兩種:
NewTimer(d Duration) *Timer
:該函數(shù)接受一個time.Duration
類型的參數(shù)d
(時間間隔),表示定時器在過期(執(zhí)行之后過期)之前等待的時間。NewTimer
返回一個新的Timer
定時器,這個定時器在其內(nèi)部維護一個通道C
,該通道在定時器被觸發(fā)時會接收當前的時間值。AfterFunc(d Duration, f func()) *Timer
:接受一個指定的時間間隔d
和回調(diào)函數(shù)f
。該函數(shù)返回一個新的Timer
定時器,在定時器到期時直接調(diào)用f
,而不是通過通道C
發(fā)送信號。調(diào)用Timer
的Stop
方法可以停止定時器和取消調(diào)用f
。
下面的代碼展示了如何使用 NewTimer
和 AfterFunc
來創(chuàng)建定時器以及定時器的基本用法:
package main import ( "fmt" "time" ) func main() { // 使用 NewTimer 創(chuàng)建一個定時器,1s后往timer.C中發(fā)送當前時間 timer := time.NewTimer(time.Second) gofunc() { select { case <-timer.C: fmt.Println("timer 定時器觸發(fā)啦!") } }() // 使用 AfterFunc 創(chuàng)建另一個定時器,1s后執(zhí)行func time.AfterFunc(time.Second, func() { fmt.Println("timer2 定時器觸發(fā)啦!") }) // 主goroutine等待兩秒,確??吹蕉〞r器觸發(fā)的輸出 time.Sleep(time.Second * 2) }
代碼運行結(jié)果如下所示:
timer 定時器觸發(fā)啦!
timer2 定時器觸發(fā)啦!
下面是代碼的逐步解析:
- 首先使用
NewTimer
創(chuàng)建了一個定時器,然后在一個新的goroutine
中監(jiān)聽它的C
屬性以等待定時器觸發(fā)。 - 其次,使用
AfterFunc
創(chuàng)建另一個定時器,通過指定一個 回調(diào)函數(shù) 來處理定時器到期事件。 - 最后,主
goroutine
等待足夠長的時間以確保定時器的觸發(fā)信息能夠被打印出來。
方法詳解
ResetReset(d Duration) bool
:該方法用于重置 Timer
定時器的過期時間,也可以理解為重新激活定時器。它接受一個 time.Duration
類型的參數(shù) d
,表示定時器在過期之前等待的時間。
除此之外,該方法還返回一個 bool
值:
- 如果定時器處于活動的狀態(tài),返回
true
。 - 如果定時器已經(jīng)過期或被停止了,返回
false
(false
并不意味著激活定時器失敗,只是標識定時器的當前狀態(tài))。
下面是代碼示例:
package main import ( "fmt" "time" ) func main() { timer := time.NewTimer(5 * time.Second) // 第一次重置,定時器處于激活狀態(tài),因此返回 true b := timer.Reset(1 * time.Second) fmt.Println(b) // true second := time.Now().Second() select { case t := <-timer.C: fmt.Println(t.Second() - second) // 1s } // 第二次重置,定時器已經(jīng)處于過期狀態(tài),因此返回 false b = timer.Reset(2 * time.Second) fmt.Println(b) // false second = time.Now().Second() select { case t := <-timer.C: fmt.Println(t.Second() - second) // 2s } }
代碼運行結(jié)果如下所示:
true
1
false
2
下面是代碼的逐步解析:
- 首先,創(chuàng)建了一個定時器,設(shè)置為
5
秒后到期。 - 然后調(diào)用
Reset
方法立即將其重置為1
秒后到期。因為此時定時器仍處于激活狀態(tài)(即還未到期),所以Reset
方法返回true
。 - 接下來的
select
語句等待定時器到期,并打印出實際經(jīng)過的秒數(shù)(約等于1
秒)。 - 接著第二次重置定時器,這次設(shè)置為
2
秒后到期。由于定時器在這次重置時已經(jīng)到期,Reset
方法返回false
。 - 最后,再次使用
select
語句等待定時器到期,并打印出這次經(jīng)過的秒數(shù)(約等于2
秒)。
Stop
Stop() bool
:該方法用于停止定時器。如果定時器停止成功,返回 true
,如果定時器已經(jīng)過期或被已經(jīng)被停止過,則返回 false
。切記:Stop 操作不會關(guān)閉通道 C。這意味著無論是通過 for select 還是 for range 去監(jiān)聽 ticker.C,我們需要使用其他機制來退出循環(huán),例如使用 context 上下文。
下面是代碼示例:
package main import ( "fmt" "time" ) func main() { timer := time.NewTimer(3 * time.Second) // 停止定時器,在定時器觸發(fā)之前停止它,因此返回 true stop := timer.Stop() fmt.Println(stop) // true stop = timer.Stop() // 第二次停止定時器,此時定時器已經(jīng)被停止了,返回 false fmt.Println(stop) // false }
代碼運行結(jié)果如下所示:
true
false
下面是代碼的逐步解析:
- 首先,創(chuàng)建了一個設(shè)置為
3
秒后觸發(fā)的定時器。 - 然后立即調(diào)用
Stop
方法停止定時器。因為此時定時器還未觸發(fā),所以Stop
返回true
。 - 最后再次調(diào)用
Stop
方法嘗試停止同一個定時器。由于定時器已經(jīng)被停止,這次Stop
返回false
。
三:Ticker:周期性定時器
Tciker
是一個周期性的定時器,用于在固定的時間間隔重復執(zhí)行任務。它在每個間隔時間到來時,向其通道(Channel
)發(fā)送當前時間。
基本使用
我們可以使用 NewTicker
函數(shù)來創(chuàng)建一個新的 Ticker
對象,該函數(shù)接受一個 time.Duration
類型的參數(shù) d
(時間間隔)。
下面是代碼示例:
package main import ( "context" "fmt" "time" ) func main() { // 每隔1秒往ticker.C中發(fā)送當前時間 ticker := time.NewTicker(time.Second) defer ticker.Stop() // 使用context控制下面的for select退出 timeout, cancelFunc := context.WithTimeout(context.Background(), time.Second*5) defer cancelFunc() go func() { for { select { case <-timeout.Done(): fmt.Println("timeout done") return case <-ticker.C: fmt.Println("定時器觸發(fā)啦!") } } }() // 主goroutine等待 7 秒,確保看到定時器觸發(fā)的輸出 time.Sleep(time.Second * 7) }
代碼運行結(jié)果如下所示:
定時器觸發(fā)啦!
定時器觸發(fā)啦!
定時器觸發(fā)啦!
定時器觸發(fā)啦!
定時器觸發(fā)啦!
timeout done
下面是代碼的逐步解析:
- 首先,創(chuàng)建了一個每秒觸發(fā)的定時器,確保函數(shù)周期結(jié)束后清理定時器,我們應該加上
defer ticker.Stop()
- 然后,創(chuàng)建一個在
5
秒后超時的上下文。cancelFunc
被用于在退出前清理上下文。 - 接著,在一個新的
goroutine
中,select
語句用于監(jiān)聽兩個通道:定時器的通道 (ticker.C
) 和超時上下文的完成通道 (timeout.Done()
)。當定時器每秒觸發(fā)時,會打印出消息。當上下文超時(即5
秒過后),打印出超時信息,并返回,從而結(jié)束該goroutine
。 - 最后,主
goroutine
通過time.Sleep(time.Second * 7)
等待7
秒,以確保能夠觀察到定時器觸發(fā)和超時事件的輸出。
除了使用 select
語句監(jiān)聽 ticker.C
以外,我們還可以使用 for range
的形式進行監(jiān)聽:
for range ticker.C {}
需要注意的是,即使通過 Stop 方法停止 Ticker 定時器,其 C 通道不會被關(guān)閉。這意味著無論是通過 for select 還是 for range 去監(jiān)聽 ticker.C,我們需要使用其他機制來退出循環(huán),例如使用 context 上下文。
方法詳解
ResetReset(d Duration)
方法用于停止計時器并將其周期重置為指定的時間。下一個時間刻度將在新周期結(jié)束后生效。它接受一個 time.Duration
類型的參數(shù) d
,表示新的周期。該參數(shù)必須大于零;否則 Reset
方法內(nèi)部將會 panic
。
下面是代碼示例:
package main import ( "time" ) func main() { ticker := time.NewTicker(5 * time.Second) defer ticker.Stop() // 重置定時器 ticker.Reset(1 * time.Second) second := time.Now().Second() for t := range ticker.C { // 1s fmt.Printf("周期:%d 秒", t.Second()-second) break } }
代碼運行結(jié)果如下所示:
周期:1 秒
下面是代碼的逐步解析:
- 首先,創(chuàng)建了一個每
5
秒觸發(fā)一次的定時器time.Ticker
。 - 其次,使用
Reset
方法重置定時器的觸發(fā)間隔。5
秒變成1
秒。 - 最后,通過一次循環(huán),打印定時器的周期,預期結(jié)果為
1
秒。
StopStop()
方法用于停止定時器。在 Stop
之后,將不再發(fā)送更多的 tick
給其通道 C
。切記:Stop 操作不會關(guān)閉通道 C。
下面是代碼示例:
package main import ( "fmt" "time" ) func main() { ticker := time.NewTicker(time.Second) quit := make(chanstruct{}) // 創(chuàng)建一個退出通道 go func() { for { select { // ticker Stop后,不會往ticker.C中發(fā)送當前時間了,并且ticker.C并沒有關(guān)閉,所以Stop后,不會再走該case了 case <-ticker.C: fmt.Println("定時器觸發(fā)啦!") // 關(guān)閉的chan是可以讀的,讀完chan中已有數(shù)據(jù)后,可以一直讀出對應類型的零值,所以一旦quit被close,quit立馬可讀 case <-quit: fmt.Println("協(xié)程停止啦!") return// 接收到退出信號,退出循環(huán) } } }() time.Sleep(time.Second * 3) ticker.Stop() // 停止定時器 close(quit) // 發(fā)送退出信號 fmt.Println("定時器停止啦!") }
代碼運行結(jié)果如下所示:
定時器觸發(fā)啦!
定時器觸發(fā)啦!
定時器觸發(fā)啦!
協(xié)程停止啦!
定時器停止啦!
- 首先,創(chuàng)建一個每秒觸發(fā)一次的
time.Ticker
對象。同時,引入了一個類型為chan struct{}
的退出通道quit
。這個通道將用于向運行中的goroutine
發(fā)送停止信號。 - 其次,啟動一個新的
goroutine
。在這個goroutine
中,使用for-select
循環(huán)來監(jiān)聽兩個事件:定時器的觸發(fā)(case <-ticker.C
)和退出信號(case <-quit
)。每當定時器觸發(fā)時,它會打印一條消息。如果收到退出信號,它會打印一條消息并退出循環(huán)。 - 接著,在主
goroutine
中,time.Sleep(time.Second * 3)
模擬了一段等待時間(3
秒),在這期間定時器會觸發(fā)幾次。 - 最后,主
goroutine
通過調(diào)用Stop
方法停止定時器,然后關(guān)閉退出通道。goroutine
接收到退出信號后打印出一條消息并退出循環(huán)。
Stop
不會關(guān)閉其通道 C
,因此我們需要借助其他方式(例如退出信號)來清理資源。
四:Timer 和 Ticker 的主要區(qū)別
用途:
Timer
用于單次延遲執(zhí)行任務。Ticker
重復執(zhí)行任務。
行為特點:
Timer
在設(shè)定的延遲時間過后觸發(fā)一次,發(fā)送一個時間值到其通道。Ticker
按照設(shè)定的間隔周期性地觸發(fā),反復發(fā)送時間值到其通道。
可控性:
Timer
可以被重置(Reset
方法)和停止(Stop
方法)。Reset
用于改變Timer
的觸發(fā)時間。Ticker
可以被重置(Reset
方法)和停止(Stop
方法)。Reset
用于改變Ticker
觸發(fā)的時間間隔。
結(jié)束操作:
Timer
的Stop
方法用于阻止Timer
觸發(fā),如果Timer
已經(jīng)觸發(fā),Stop
不會從其通道中刪除已發(fā)送的時間值。Ticker
的Stop
方法用于停止Ticker
的周期性觸發(fā),一旦停止,它不會再向通道發(fā)送新的值。
注意事項
- 無論是
Timer
還是Ticker
定時器,調(diào)用Stop
方法之后,并不會關(guān)閉它們的C
通道。如果有其他的goroutine
在監(jiān)聽這個通道,為避免潛在的內(nèi)存泄漏,需要手動結(jié)束該goroutine
。通常,這種資源釋放的問題可以通過使用context
或通過關(guān)閉信號(利用Channel
實現(xiàn))來解決。 - 當
Ticker
定時器完成其任務后,為了防止內(nèi)存泄漏,應調(diào)用Stop
方法來釋放相關(guān)資源。如果未及時停止Ticker
,可能導致資源持續(xù)占用。 - 在編寫
Go
代碼時,我們應根據(jù)不同的應用場景去選擇合適的定時器。同時,我們應遵循良好的規(guī)范,特別是在定時器使用完畢后及時釋放資源,對于避免潛在的內(nèi)存泄漏問題尤為重要。
到此這篇關(guān)于Go中的Timer 和 Ticker的文章就介紹到這了,更多相關(guān)Go Timer 和 Ticker內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go內(nèi)存分配之結(jié)構(gòu)體優(yōu)化技巧
這篇文章主要為大家詳細介紹了Go語言內(nèi)存分配之結(jié)構(gòu)體優(yōu)化技巧的相關(guān)知識,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下2023-11-11Redis?BloomFilter布隆過濾器原理與實現(xiàn)
你在開發(fā)或者面試過程中,有沒有遇到過?海量數(shù)據(jù)需要查重,緩存穿透怎么避免等等這樣的問題呢?下面這個東西超棒,好好了解下,面試過關(guān)斬將,凸顯你的不一樣2022-10-10