Golang time.Timer and time.Ticker

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í)候都是通過 NewTimerAfterFunc 函數(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_settimetimer_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直接是返回了Timerchannel,這種就可以做超時(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í)器,ResetTimer還未觸發(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)單了,TimerTicker結(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 的封裝

TickerTimer 類似,區(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í)器

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容