首頁 > 軟體

golang 一次性定時器Timer用法及實現原理詳解

2022-08-25 14:01:51

前言

定時器在Go語言應用中使用非常廣泛,Go語言的標準庫裡提供兩種型別的計時器,一種是一次性的定時器Timer,另外一種是週期性的定時Ticker。本文主要來看一下Timer的用法和實現原理,需要的朋友可以參考以下內容,希望對大家有幫助。

Timer

Timer是一種單一事件的定時器,即經過指定的時間後觸發一個事件,因為Timer只執行一次就結束,所以稱為單一事件,這個事件通過其本身提供的channel進行通知觸發。

timer結構體

通過src/time.sleep.go:Timer定義了Timer資料結構:

// Timer代表一次定時,時間到達後僅執行一個事件。
type Timer struct {
    C <-chan Time
    r runtimeTimer
}

它提供了一個channel,在定時時間到達之前,沒有資料寫入timer.C會一直阻塞,直到時間到達,向channel寫入系統時間,阻塞解除,可以從中讀取資料,這就是一個事件。

建立定時器

func NewTimer(d Duration) *Timer

通過上面方法指定一個事件即可建立一個Timer,Timer一經建立便開始計時,不需要額外的啟動命令。

範例:

func main()  {
	timer := time.NewTimer(time.Second * 5) //設定超時時間5s
	&lt;- timer.C
	fmt.Println("Time out!")
}

停止定時器

Timer建立後可以隨時停止,停止計時器的方法如下:

func (t *Timer) Stop() bool

其返回值代表定時器有沒有超時:

  • true:定時器超時前停止,後續不會再有事件傳送。
  • false:定時器超時後停止。

範例:

func main()  {
	timer := time.NewTimer(time.Second * 5) //設定超時時間5s
    timer.Stop()
}

重置定時器

已經過期的定時器或者已停止的定時器,可以通過重置動作重新啟用,方法如下:

func (t *Timer) Reset(d Duration) bool

重置的動作本質上是先停掉定時器,再啟動,其返回值也即是停掉計時器的返回值。

func main()  {
	timer := time.NewTimer(time.Second * 5)
	&lt;- timer.C
	fmt.Println("Time out!")
	timer.Stop() 
	timer.Reset(time.Second*3)  // 重置定時器
}

實現原理

每個Go應用程式都有一個協程專門負責管理所有的Timer,這個協程負責監控Timer是否過期,過期後執行一個預定義的動作,這個動作對於Timer而言就是傳送當前時間到管道中。

資料結構

type Timer struct {
    C <-chan Time
    r runtimeTimer
}

Timer只有兩個成員:

  • C:channel,上層應用根據此管道接收事件;
  • r:runtimeTimer定時器,該定時器即系統管理的定時器,上層應用不可見。

runtimeTimer

任務的載體,用於監控定時任務,每建立一個Timer就建立一個runtimeTimer變數,然後把它交給系統進行監控,我們通過設定runtimeTimer過期後的行為來達到定時的目的。

原始碼包src/time/sleep.go:runtimeTimer定義了其資料結構:

type runtimeTimer struct {
    tb uintptr                          // 儲存當前定時器的陣列地址
    i  int                              // 儲存當前定時器的陣列下標
    when   int64                        // 當前定時器觸發時間
    period int64                        // 當前定時器週期觸發間隔
    f      func(interface{}, uintptr)   // 定時器觸發時執行的函數
    arg    interface{}                  // 定時器觸發時執行函數傳遞的引數一
    seq    uintptr                      // 定時器觸發時執行函數傳遞的引數二(該引數只在網路收發場景下使用)
}

建立Timer

原始碼實現:

func NewTimer(d Duration) *Timer {
    c := make(chan Time, 1)  // 建立一個管道
    t := &Timer{ // 構造Timer資料結構
        C: c,               // 新建立的管道
        r: runtimeTimer{
            when: when(d),  // 觸發時間
            f:    sendTime, // 觸發後執行函數sendTime
            arg:  c,        // 觸發後執行函數sendTime時附帶的引數
        },
    }
    startTimer(&t.r) // 此處啟動定時器,只是把runtimeTimer放到系統協程的堆中,由系統協程維護
    return t
}
  • NewTimer()只是構造了一個Timer,然後把Timer.r通過startTimer()交給系統協程維護。
  • C 是一個帶1個容量的chan,這樣做有什麼好處呢,原因是chan 無緩衝傳送資料就會阻塞,阻塞系統協程,這顯然是不行的。
  • 回撥函數設定為sendTime,執行引數為channelsendTime就是到點往C 裡面傳送當前時間的函數

sendTime實現:

//c interface{} 就是NewTimer 賦值的引數,就是channel
func sendTime(c interface{}, seq uintptr) {
    select {
    case c.(chan Time) <- Now(): //寫不進去的話,C 已滿,走default 分支
    default:
    }
}

停止Timer

停止Timer,就是把Timer從系統協程中移除。函數主要實現如下:

func (t *Timer) Stop() bool {
    return stopTimer(&t.r)
}

stopTimer()即通知系統協程把該Timer移除,即不再監控。系統協程只是移除Timer並不會關閉管道,以避免使用者協程讀取錯誤。

重置Timer

重置Timer時會先把timer從系統協程中刪除,修改新的時間後重新新增到系統協程中。

func (t *Timer) Reset(d Duration) bool {
    w := when(d)
    active := stopTimer(&t.r)
    t.r.when = w
    startTimer(&t.r)
    return active
}

以上就是golang 一次性定時器Timer用法及實現原理詳解的詳細內容,更多關於go 一次性定時器Timer的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com