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

