golang定時器Timer的用法和實現(xiàn)原理解析
一文搞懂golang定時器Timer的用法和實現(xiàn)原理
前言
定時器在Go語言應(yīng)用中使用非常廣泛,Go語言的標(biāo)準(zhǔn)庫里提供兩種類型的計時器,一種是一次性的定時器Timer,另外一種是周期性的定時器Ticker。本文主要來看一下Timer的用法和實現(xiàn)原理,需要的朋友可以參考以下內(nèi)容,希望對大家有幫助。
Timer
Timer是一種單一事件的定時器,即經(jīng)過指定的時間后觸發(fā)一個事件,因為Timer只執(zhí)行一次就結(jié)束,所以稱為單一事件,這個事件通過其本身提供的channel進行通知觸發(fā)。
timer結(jié)構(gòu)體
通過src/time.sleep.go:Timer定義了Timer數(shù)據(jù)結(jié)構(gòu):
// Timer代表一次定時,時間到達后僅執(zhí)行一個事件。
type Timer struct {
C <-chan Time
r runtimeTimer
}它提供了一個channel,在定時時間到達之前,沒有數(shù)據(jù)寫入timer.C會一直阻塞,直到時間到達,向channel寫入系統(tǒng)時間,阻塞解除,可以從中讀取數(shù)據(jù),這就是一個事件。
創(chuàng)建定時器
func NewTimer(d Duration) *Timer
通過上面方法指定一個事件即可創(chuàng)建一個Timer,Timer一經(jīng)創(chuàng)建便開始計時,不需要額外的啟動命令。
示例:
func main() {
timer := time.NewTimer(time.Second * 5) //設(shè)置超時時間5s
<- timer.C
fmt.Println("Time out!")
}停止定時器
Timer創(chuàng)建后可以隨時停止,停止計時器的方法如下:
func (t *Timer) Stop() bool
其返回值代表定時器有沒有超時:
- true:定時器超時前停止,后續(xù)不會再有事件發(fā)送。
- false:定時器超時后停止。
示例:
func main() {
timer := time.NewTimer(time.Second * 5) //設(shè)置超時時間5s
timer.Stop()
}重置定時器
已經(jīng)過期的定時器或者已停止的定時器,可以通過重置動作重新激活,方法如下:
func (t *Timer) Reset(d Duration) bool
重置的動作本質(zhì)上是先停掉定時器,再啟動,其返回值也即是停掉計時器的返回值。
func main() {
timer := time.NewTimer(time.Second * 5)
<- timer.C
fmt.Println("Time out!")
timer.Stop()
timer.Reset(time.Second*3) // 重置定時器
}實現(xiàn)原理
每個Go應(yīng)用程序都有一個協(xié)程專門負責(zé)管理所有的Timer,這個協(xié)程負責(zé)監(jiān)控Timer是否過期,過期后執(zhí)行一個預(yù)定義的動作,這個動作對于Timer而言就是發(fā)送當(dāng)前時間到管道中。
數(shù)據(jù)結(jié)構(gòu)
type Timer struct {
C <-chan Time
r runtimeTimer
}Timer只有兩個成員:
- C:channel,上層應(yīng)用根據(jù)此管道接收事件;
- r:runtimeTimer定時器,該定時器即系統(tǒng)管理的定時器,上層應(yīng)用不可見。
runtimeTimer
任務(wù)的載體,用于監(jiān)控定時任務(wù),每創(chuàng)建一個Timer就創(chuàng)建一個runtimeTimer變量,然后把它交給系統(tǒng)進行監(jiān)控,我們通過設(shè)置runtimeTimer過期后的行為來達到定時的目的。
源碼包src/time/sleep.go:runtimeTimer定義了其數(shù)據(jù)結(jié)構(gòu):
type runtimeTimer struct {
tb uintptr // 存儲當(dāng)前定時器的數(shù)組地址
i int // 存儲當(dāng)前定時器的數(shù)組下標(biāo)
when int64 // 當(dāng)前定時器觸發(fā)時間
period int64 // 當(dāng)前定時器周期觸發(fā)間隔
f func(interface{}, uintptr) // 定時器觸發(fā)時執(zhí)行的函數(shù)
arg interface{} // 定時器觸發(fā)時執(zhí)行函數(shù)傳遞的參數(shù)一
seq uintptr // 定時器觸發(fā)時執(zhí)行函數(shù)傳遞的參數(shù)二(該參數(shù)只在網(wǎng)絡(luò)收發(fā)場景下使用)
}創(chuàng)建Timer
源碼實現(xiàn):
func NewTimer(d Duration) *Timer {
c := make(chan Time, 1) // 創(chuàng)建一個管道
t := &Timer{ // 構(gòu)造Timer數(shù)據(jù)結(jié)構(gòu)
C: c, // 新創(chuàng)建的管道
r: runtimeTimer{
when: when(d), // 觸發(fā)時間
f: sendTime, // 觸發(fā)后執(zhí)行函數(shù)sendTime
arg: c, // 觸發(fā)后執(zhí)行函數(shù)sendTime時附帶的參數(shù)
},
}
startTimer(&t.r) // 此處啟動定時器,只是把runtimeTimer放到系統(tǒng)協(xié)程的堆中,由系統(tǒng)協(xié)程維護
return t
}NewTimer()只是構(gòu)造了一個Timer,然后把Timer.r通過startTimer()交給系統(tǒng)協(xié)程維護。- C 是一個帶1個容量的chan,這樣做有什么好處呢,原因是chan 無緩沖發(fā)送數(shù)據(jù)就會阻塞,阻塞系統(tǒng)協(xié)程,這顯然是不行的。
- 回調(diào)函數(shù)設(shè)置為
sendTime,執(zhí)行參數(shù)為channel,sendTime就是到點往C 里面發(fā)送當(dāng)前時間的函數(shù)
sendTime實現(xiàn):
//c interface{} 就是NewTimer 賦值的參數(shù),就是channel
func sendTime(c interface{}, seq uintptr) {
select {
case c.(chan Time) <- Now(): //寫不進去的話,C 已滿,走default 分支
default:
}
}停止Timer
停止Timer,就是把Timer從系統(tǒng)協(xié)程中移除。函數(shù)主要實現(xiàn)如下:
func (t *Timer) Stop() bool {
return stopTimer(&t.r)
}stopTimer()即通知系統(tǒng)協(xié)程把該Timer移除,即不再監(jiān)控。系統(tǒng)協(xié)程只是移除Timer并不會關(guān)閉管道,以避免用戶協(xié)程讀取錯誤。
重置Timer
重置Timer時會先把timer從系統(tǒng)協(xié)程中刪除,修改新的時間后重新添加到系統(tǒng)協(xié)程中。
func (t *Timer) Reset(d Duration) bool {
w := when(d)
active := stopTimer(&t.r)
t.r.when = w
startTimer(&t.r)
return active
}補充:golang定時器Ticker
time包下有一個Ticker結(jié)構(gòu)體
// Ticker保管一個通道,并每隔一段時間向其傳遞"tick"。
type Ticker struct {
C <-chan Time // 周期性傳遞時間信息的通道.
r runtimeTimer
}func NewTicker(d Duration) *Ticker{}NewTicker返回一個新的Ticker,該Ticker包含一個通道字段,并會每隔時間段d,就向該通道發(fā)送當(dāng)時的時間。它會調(diào)整時間間隔或者丟棄tick信息以適應(yīng)反應(yīng)慢的接收者。如果d<=0會panic。關(guān)閉該Ticker可以釋放相關(guān)資源。
func (t *Ticker) Stop()
Stop關(guān)閉一個Ticker。在關(guān)閉后,將不會發(fā)送更多的tick信息。Stop不會關(guān)閉通道t.C,以避免從該通道的讀取不正確的成功。
例子
package main
import (
"fmt"
"time"
)
func main() {
t := time.NewTicker(5 * time.Second) //創(chuàng)建定時器
defer t.Stop()
go sync(t)
select {
}
}
func sync(t *time.Ticker) {
for {
// 每5秒中從chan t.C 中讀取一次
<-t.C
fmt.Println("執(zhí)行數(shù)據(jù)備份任務(wù):", time.Now().Format("2006-01-02 15:04:05"))
}
}到此這篇關(guān)于golang定時器Timer的用法和實現(xiàn)原理解析的文章就介紹到這了,更多相關(guān)golang定時器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解如何在Go中循環(huán)中使用Defer關(guān)鍵字示例詳解
這篇文章主要為大家介紹了詳解如何在Go中循環(huán)中使用Defer關(guān)鍵字示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-09-09
簡單聊聊Golang中defer預(yù)計算參數(shù)
在golang當(dāng)中defer代碼塊會在函數(shù)調(diào)用鏈表中增加一個函數(shù)調(diào)用,下面這篇文章主要給大家介紹了關(guān)于Golang中defer預(yù)計算參數(shù)的相關(guān)資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下2022-03-03

