time.Timer
結(jié)構(gòu)
首先我們看Timer的結(jié)構(gòu)定義:
type Timer struct {
C <-chan Time
r runtimeTimer
}
其中有一個(gè)C的只讀channel,還有一個(gè)runtimeTimer類型的結(jié)構(gòu)體,再看一下這個(gè)結(jié)構(gòu)的具體結(jié)構(gòu):
type runtimeTimer struct {
tb uintptr
i int
when int64
period int64
f func(interface{}, uintptr) // NOTE: must not be closure
arg interface{}
seq uintptr
}
在使用定時(shí)器Timer的時(shí)候都是通過 NewTimer 或 AfterFunc 函數(shù)來獲取。
先來看一下NewTimer的實(shí)現(xiàn):
func NewTimer(d Duration) *Timer {
c := make(chan Time, 1)
t := &Timer{
C: c,
r: runtimeTimer{
when: when(d), //表示達(dá)到時(shí)間段d時(shí)候調(diào)用f
f: sendTime, // f表示一個(gè)函數(shù)調(diào)用,這里的sendTime表示d時(shí)間到達(dá)時(shí)向Timer.C發(fā)送當(dāng)前的時(shí)間
arg: c, // arg表示在調(diào)用f的時(shí)候把參數(shù)arg傳遞給f,c就是用來接受sendTime發(fā)送時(shí)間的
},
}
startTimer(&t.r)
return t
}
定時(shí)器的具體實(shí)現(xiàn)邏輯,都在
runtime中的time.go中,它的實(shí)現(xiàn),沒有采用經(jīng)典Unix間隔定時(shí)器setitimer系統(tǒng)調(diào)用,也沒有 采用POSIX間隔式定時(shí)器(相關(guān)系統(tǒng)調(diào)用:timer_create、timer_settime和timer_delete),而是通過四叉樹堆(heep)實(shí)現(xiàn)的(runtimeTimer結(jié)構(gòu)中的i字段,表示在堆中的索引)。通過構(gòu)建一個(gè)最小堆,保證最快拿到到期了的定時(shí)器執(zhí)行。定時(shí)器的執(zhí)行,在專門的goroutine中進(jìn)行的:go timerproc()。有興趣的同學(xué),可以閱讀runtime/time.go的源碼。
其他方法
func After(d Duration) <-chan Time { return NewTimer(d).C }
根據(jù)源碼可以看到After直接是返回了Timer的channel,這種就可以做超時(shí)處理。
比如我們有這樣一個(gè)需求:我們寫了一個(gè)爬蟲,爬蟲在HTTP GET 一個(gè)網(wǎng)頁(yè)的時(shí)候可能因?yàn)榫W(wǎng)絡(luò)的原因就一只等待著,這時(shí)候就需要做超時(shí)處理,比如只請(qǐng)求五秒,五秒以后直接丟掉不請(qǐng)求這個(gè)網(wǎng)頁(yè)了,或者重新發(fā)起請(qǐng)求。
go Get("http://baidu.com/")
func Get(url string) {
response := make(chan string)
response = http.Request(url)
select {
case html :=<- response:
println(html)
case <-time.After(time.Second * 5):
println("超時(shí)處理")
}
}
可以從代碼中體現(xiàn)出來,如果五秒到了,網(wǎng)頁(yè)的請(qǐng)求還沒有下來就是執(zhí)行超時(shí)處理,因?yàn)?code>Timer的內(nèi)部會(huì)是幫你在你設(shè)置的時(shí)間長(zhǎng)度后自動(dòng)向Timer.C中寫入當(dāng)前時(shí)間。
其實(shí)也可以寫成這樣:
func Get(url string) {
response := make(chan string)
response = http.Request(url)
timeOut := time.NewTimer(time.Second * 3)
select {
case html :=<- response:
println(html)
case <-timeOut.C:
println("超時(shí)處理")
}
}
-
func (t *Timer) Reset(d Duration) bool//強(qiáng)制的修改timer中規(guī)定的時(shí)間,Reset會(huì)先調(diào)用stopTimer再調(diào)用startTimer,類似于廢棄之前的定時(shí)器,重新啟動(dòng)一個(gè)定時(shí)器,Reset在Timer還未觸發(fā)時(shí)返回true;觸發(fā)了或Stop了,返回false。 -
func (t *Timer) Stop() bool// 如果定時(shí)器還未觸發(fā),Stop會(huì)將其移除,并返回true;否則返回false;后續(xù)再對(duì)該Timer調(diào)用Stop,直接返回false。
比如我寫了了一個(gè)簡(jiǎn)單的事例:每?jī)擅虢o你的女票發(fā)送一個(gè)"I Love You!"
// 其中協(xié)程之間的控制做的不太好,可以使用channel或者golang中的context來控制
package main
import (
"time"
"fmt"
)
func main() {
go Love() // 起一個(gè)協(xié)程去執(zhí)行定時(shí)任務(wù)
stop := 0
for {
fmt.Scan(&stop)
if stop == 1{
break
}
}
}
func Love() {
timer := time.NewTimer(2 * time.Second) // 新建一個(gè)Timer
for {
select {
case <-timer.C:
fmt.Println("I Love You!")
timer.Reset(2 * time.Second) // 上一個(gè)when執(zhí)行完畢重新設(shè)置
}
}
return
}
-
func AfterFunc(d Duration, f func()) *Timer// 在時(shí)間d后自動(dòng)執(zhí)行函數(shù)f
func main() {
f := func(){fmt.Println("I Love You!")}
time.AfterFunc(time.Second*2, f)
time.Sleep(time.Second * 4)
}
自動(dòng)在2秒后打印 "I Love You!"
time.Ticker
如果學(xué)會(huì)了Timer那么Ticker就很簡(jiǎn)單了,Timer和Ticker結(jié)構(gòu)體的結(jié)構(gòu)是一樣的,舉一反三,其實(shí)Ticker就是一個(gè)重復(fù)版本的Timer,它會(huì)重復(fù)的在時(shí)間d后向Ticker中寫數(shù)據(jù)
-
func NewTicker(d Duration) *Ticker// 新建一個(gè)Ticker -
func (t *Ticker) Stop()// 停止Ticker -
func Tick(d Duration) <-chan Time// Ticker.C 的封裝
Ticker 和 Timer 類似,區(qū)別是:Ticker 中的runtimeTimer字段的 period 字段會(huì)賦值為 NewTicker(d Duration) 中的d,表示每間隔d納秒,定時(shí)器就會(huì)觸發(fā)一次。
除非程序終止前定時(shí)器一直需要觸發(fā),否則,不需要時(shí)應(yīng)該調(diào)用 Ticker.Stop 來釋放相關(guān)資源。
如果程序終止前需要定時(shí)器一直觸發(fā),可以使用更簡(jiǎn)單方便的 time.Tick 函數(shù),因?yàn)?Ticker 實(shí)例隱藏起來了,因此,該函數(shù)啟動(dòng)的定時(shí)器無法停止。
那么這樣我們就可以把發(fā)"I Love You!"的例子寫得簡(jiǎn)單一些。
func main() {
//定義一個(gè)ticker
ticker := time.NewTicker(time.Millisecond * 500)
//Ticker觸發(fā)
go func() {
for t := range ticker.C {
fmt.Println(t)
fmt.Println("I Love You!")
}
}()
time.Sleep(time.Second * 18)
//停止ticker
ticker.Stop()
}
定時(shí)器的實(shí)際應(yīng)用
在實(shí)際開發(fā)中,定時(shí)器用的較多的會(huì)是 Timer,如模擬超時(shí),而需要類似 Tiker 的功能時(shí),可以使用實(shí)現(xiàn)了 cron spec 的庫(kù) cron。
參考
定時(shí)器