Go語言實現(xiàn)定時器的原理及使用詳解
0. 前言
在進(jìn)行并發(fā)編程時,有時候會需要定時功能,比如監(jiān)控某個GO程是否會運行過長時間、定時打印日志等等。
GO標(biāo)準(zhǔn)庫中的定時器主要有兩種,一種為Timer定時器,一種為Ticker定時器。Timer計時器使用一次后,就失效了,需要Reset()才能再次生效。而Ticker計時器會一直生效,接下來分別對兩種進(jìn)行介紹。
1. Timer定時器
首先介紹一下GO定時器的實現(xiàn)原理。
在一個GO進(jìn)程中,其中的所有計時器都是由一個運行著 timerproc() 函數(shù)的 goroutine 來保護(hù)。它使用時間堆(最小堆)的算法來保護(hù)所有的 Timer,其底層的數(shù)據(jù)結(jié)構(gòu)基于數(shù)組的最小堆,堆頂?shù)脑厥情g隔超時最近的 Timer,這個 goroutine 會定期 wake up,讀取堆頂?shù)?Timer,執(zhí)行對應(yīng)的 f 函數(shù)或者 sendtime()函數(shù)(下文會對這兩個函數(shù)進(jìn)行介紹),而后將其從堆頂移除。
接著看看Timer的結(jié)構(gòu):
type Timer struct {
C <-chan Time
// contains filtered or unexported fields
}
Timer中對外暴露的只有一個channel,這個 channel 也是定時器的核心。當(dāng)計時結(jié)束時,Timer會發(fā)送值到channel中,外部環(huán)境在這個 channel 收到值的時候,就代表計時器超時了,可與select搭配執(zhí)行一些超時邏輯。可以通過time.NewTimer、time.AfterFunc或者 time.Afte對一個Timer進(jìn)行創(chuàng)建。
1.1 time.NewTimer() 和 time.After()
1.1.1 time.NewTimer()
查看以下簡單的應(yīng)用代碼:
package main
import (
"fmt"
"time"
)
type H struct {
t *time.Timer
}
func main() {
fmt.Println("main")
h:=H{t: time.NewTimer(1*time.Second)}
go h.timer()
time.Sleep(10*time.Second)
}
func (h *H) timer() {
for {
select {
case <-h.t.C:
fmt.Println("timer")
}
}
}
我們創(chuàng)建了一個timer,設(shè)置時間為1S。然后使用一個select對timer的C進(jìn)行接受,GO程運行timer()函數(shù),會一直阻塞直到超時發(fā)生(接收到C的數(shù)據(jù)),此時打印timer。
Stop() 停止 Timer
func (t *Timer) Stop() bool
Stop() 是 Timer 的一個方法,調(diào)用 Stop()方法,會停止這個 Timer 的計時,使其失效,之后觸發(fā)定時事件。
實際上,調(diào)用此方法后,此Timer會被從時間堆中移除。
Reset()重置Timer
注意,Timer定時器超時一次后就不會再次運行,所以需要調(diào)用Reset函數(shù)進(jìn)行重置。修改select中代碼,在Case中添加一個重置的代碼:
select {
case <-h.t.C:
fmt.Println("timer")
h.t.Reset(1*time.Second)
}
可以看到,會不停的打印timer,這是因為使用了Reset函數(shù)重置定時器。
注意!不能隨意的對Reset方法進(jìn)行調(diào)用,官網(wǎng)文檔中特意強調(diào):
For a Timer created with NewTimer, Reset should be invoked only on stopped or expired timers with drained channels.
大概的意思就是說,除非Timer已經(jīng)被停止或者超時了,否則不要調(diào)用Reset方法,因為,如果這個 Timer 還沒超時,不先去Stop它,而是直接Reset,那么舊的 Timer 仍然存在,并且仍可能會觸發(fā),會產(chǎn)生一些意料之外的事。所以通常使用如下的代碼,安全的重置一個不知狀態(tài)的Timer(以上的代碼中,Reset調(diào)用時,總是處于超時狀態(tài)):
if !t.Stop() {
select {
case <-h.t.C:
default:
}
}
h.t.Reset(1*time.Second)
1.1.2 time.After()
此方法就像是一個極簡版的Timer使用,調(diào)用time.After(),會直接返回一個channel,當(dāng)超時后,此channel會接受到一個值,簡單使用如下:
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("main")
go ticker()
time.Sleep(100 * time.Second)
}
func ticker() {
for {
select {
case <-time.After(1 * time.Second):
fmt.Println("timer")
}
}
}
注意,此方法雖然簡單,但是沒有Reset方法來重置定時器,但是可以搭配for 和select的重復(fù)調(diào)用來模擬重置。
1.1.3 sendtime函數(shù)
NewTimer和After這兩種創(chuàng)建方法,會Timer在超時后,執(zhí)行一個標(biāo)準(zhǔn)庫中內(nèi)置的函數(shù):sendTime,來將當(dāng)前的時間發(fā)送到channel中。
1.2 time.AfterFunc
此方法可以接受一個func類型參數(shù),在計時結(jié)束后,會運行此函數(shù),查看以下代碼,猜猜會出現(xiàn)什么結(jié)果?
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("main")
t := time.AfterFunc(1*time.Second, func() {
fmt.Println("timer")
})
go timer(t)
time.Sleep(10 * time.Second)
}
func timer(t *time.Timer) {
select {
case <-t.C:
fmt.Println("123")
}
}
結(jié)果只打印了main以及timer。這是因為此方法并不會調(diào)用上文提到的sendtime()函數(shù),即不會發(fā)送值給Timer的Channel,所以select就會一直阻塞。
f函數(shù)
特意將AfterFunc和以上的NewTimer和After,就是因為f函數(shù)的存在。這種方式創(chuàng)建的Timer,在到達(dá)超時時間后會在單獨的goroutine里執(zhí)行函數(shù)f,而不會執(zhí)行sendtime函數(shù)。
注意,外部傳入的f參數(shù)并非直接運行在timerproc中,而是啟動了一個新的goroutine去執(zhí)行此方法。
2. Ticker定時器
Ticker定時器可以周期性地不斷地觸發(fā)時間事件,不需要額外的Reset操作。
其使用方法與Timer大同小異。通過time.NewTicker對Ticker進(jìn)行創(chuàng)建,簡單的使用如下:
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("main")
t:=time.NewTicker(1*time.Second)
go timer(t)
time.Sleep(10 * time.Second)
}
func timer(t *time.Ticker) {
for{
select {
case <-t.C:
fmt.Println("timer")
}
}
}
到此這篇關(guān)于Go語言實現(xiàn)定時器的原理及使用詳解的文章就介紹到這了,更多相關(guān)Go語言定時器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Go語言實現(xiàn)AES加密并編寫一個命令行應(yīng)用程序
密碼學(xué)中的高級加密標(biāo)準(zhǔn)(Advanced Encryption Standard,AES),又稱Rijndael加密法,是經(jīng)常采用的一種區(qū)塊加密標(biāo)準(zhǔn)。本文就來用Go語言實現(xiàn)AES加密算法,需要的可以參考一下2023-02-02
深入理解Go gin框架中Context的Request和Writer對象
這篇文章主要為大家詳細(xì)介紹了Go語言的gin框架中Context的Request和Writer對象,文中的示例代碼講解詳細(xì),對我們深入了解Go語言有一定的幫助,快跟隨小編一起學(xué)習(xí)一下吧2023-04-04
Golang調(diào)用FFmpeg轉(zhuǎn)換視頻流的實現(xiàn)
本文主要介紹了Golang調(diào)用FFmpeg轉(zhuǎn)換視頻流,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02

