1. 程式人生 > >Golang定時器斷續器

Golang定時器斷續器

定時器 pac 函數 時間 方法 計時器 RoCE mil tail

定時器

1.定時器結構

  • 結構定義

    type Timer struct {
    C <-chan Time // 接受定時器事件的通道
    r runtimeTimer
    }

    type runtimeTimer struct {
    tb uintptr
    i int

      when   int64
      period int64
      f      func(interface{}, uintptr) // NOTE: must not be closure
      arg    interface{}
      seq    uintptr

    }

2.創建定時器

  • 接口定義
    func NewTimer(d Duration) *Timer

  • 使用簡單實例
    ```go
    var timer = NewTimer(time.Second)

    go func() {
    for {
    select {
    case <-timer.C:
    fmt.Println("time out.")
    }
    }
    }()
    ```

  • NewTimer源代碼:
    ```go
    func NewTimer(d Duration) *Timer {
    c := make(chan Time, 1) // 創建一個帶有一個Time結構緩沖的通道
    t := &Timer{
    C: c,
    r: runtimeTimer{ // 運行時定時器
    when: when(d), // 定時多久

    f: sendTime, // Golang寫入時間的回調接口
    arg: c, // 往哪個通道寫入時間
    },
    }
    startTimer(&t.r) // 啟動提交定時器
    return t
    }

    // 時間到後,Golang自動調用sendTime接口,嘗試往c通道寫入時間
    func sendTime(c interface{}, seq uintptr) {
    // 給c通道以非阻塞方式發送時間
    // 如果被用於NewTimer, 無論如何不能阻塞.
    // 如果被用於NewTicker,接收方未及時接受時間,則會丟棄掉,因為發送時間是周期性的。
    select {
    case c.(chan Time) <- Now():

    default:
    }
    }

    func startTimer(*runtimeTimer)
    ```

  • 代碼實例
    ```go
    package main

    import (
    "fmt"
    "time"
    )

    func main() {

      // 創建延遲3s的定時器
      exit := make(chan bool)
      timer := time.NewTimer(3 * time.Second)
    
      go func() {
          defer func() {
              exit <- true
          }()
    
          select {
          case <-timer.C:
              fmt.Println("time out.")
              return
          }
      }()
    
      <-exit

    }
    ```

3.停止定時器

  • 接口定義
    go func (t *Timer) Stop() bool
    • 本接口可以防止計時器出觸發。如果定時器停止,則返回true,如果定時器已過期或已停止,則返回false。
      Stop不關閉通道,以防止通道讀取的操作不正確。

    • 為防止通過NewTimer創建的定時器,在調用Stop接口後觸發,檢查Stop返回值並清空通道。如:
      go if !t.Stop() { <-t.C }
      但不能與Timer的通道中的其他接受同時進行!!!

    • 對於使用AfterFunc(d, f)創建的定時器,如果t.Stop()返回false,則定時器已經過期,並且函數f已經在自己的協程中啟動。
      無論函數f是否執行完成,Stop()返回不會阻塞,如果用戶需要知道f是否執行完畢,必須明確地與f協調。

  • 內部使用接口
    go func stopTimer(*runtimeTimer) bool

  • 代碼實例
    ```go
    package main

    import (
    "fmt"
    "time"
    )

    func main() {
    timer := time.NewTimer(time.Second)
    time.Sleep(time.Millisecond * 500)
    timer.Stop()

      fmt.Println("timer stopped")
      time.Sleep(time.Second * 3)

    }
    ```

4.重置定時器

  • 接口定義
    go func (t *Timer) Reset(d Duration) bool

    • 定時器被激活,返回true,若定時器已過期或已被停止,返回false。

    • 若程序已從t.C中接受數據,定時器已知過期,t.Rest()可直接使用

    • 若程序還尚未從t.C收到一個值,則必須停止定時器。如果Stop提示計時器在停止之前已過期,則應明確清空通道。
      go if !t.Stop() { <-t.c } t.Rest(d)
  • 代碼實例
    ```go
    package main

    import (
    "fmt"
    "time"
    )

    func doTimer(t *time.Timer, exit chan<- bool) {

      go func(t *time.Timer) {
          defer func() {
              exit <- true
          }()
    
          for {
              select {
              case c := <-t.C:
                  fmt.Println("timer timeout at", c)
                  return
              }
          }
      }(t)

    }

    func main() {
    sign := make(chan bool)
    timer := time.NewTimer(time.Second * 3)

      doTimer(timer, sign)
      time.Sleep(time.Second)
    
      // 實際測試:註釋下面三行代碼,效率一樣。
      if !timer.Stop() {
          <-timer.C
      }
    
      timer.Reset(time.Second * 3)
      fmt.Println("timer reset at", time.Now())
    
      <-sign

    }

    ```

5.After接口

  • 接口定義
    go func After(d Duration) <-chan Time
    • time.After函數,表示多少時間,寫入當前時間,在取出channel時間之前不會阻塞,後續程序可以繼續執行
    • time.After函數,通常用於處理程序超時問題

    • 等待一段時間d後,Golang會發送當前時間到返回的通道上。
    • 底層的定時器不會被GC回收,如果考慮效率,可使用NewTimer創建定時器,如果不需要,則調用Timer.Stop

  • 源碼實例
    ```
    package main

    import (
    "fmt"
    "time"
    )

    func main() {
    sign := make(chan bool)
    chan1 := make(chan int)
    chan2 := make(chan int)

      defer func() {
          close(sign)
          close(chan1)
          close(chan2)
      }()
    
      go func() {
          for {
              select {
              case c := <-time.After(time.Second * 3):
                  fmt.Println("After at", c)
                  // 若不往sign通道寫入數據,程序循環每隔3s執行當前case分支。
                  sign <- true
              case c1 := <-chan1:
                  fmt.Println("c1", c1)
              case c2 := <-chan2:
                  fmt.Println("c1", c2)
              }
          }
      }()
    
      <-sign

    }
    ```

6.AfterFun接口

  • 接口定義
    go func AfterFunc(d Duration, f func()) *Timer

    • 等待一段時間d後,Golang會在自己的協程中調用f。並返回一個定時器,可以使用Stop方法取消調用
  • 代碼實例
    ```go
    package main

    import (
    "fmt"
    "time"
    )

    func main() {

      timer := time.AfterFunc(time.Second*3, func() {
          fmt.Println("AfterFunc Callback")
      })
    
      time.Sleep(time.Second * 5)
      timer.Stop()

    }
    ```

斷續器

  • 斷續器(滴答器)持有一個通道,該通道每隔一段時間發送時鐘的滴答

  • 註1:從已經關閉的斷續器中讀取數據發生報錯。所以在退出處理斷續器流程前,需要先取消斷續器。

  • 註2:經過取消的斷續器,不能再復用,需要重新創建一個新的斷續器。

  • 結構定義如下:
    ```go
    type Ticker struct {
    C <-chan Time // The channel on which the ticks are delivered.
    r runtimeTimer
    }

    type runtimeTimer struct {
    tb uintptr
    i int

      when   int64
      period int64
      f      func(interface{}, uintptr) // NOTE: must not be closure
      arg    interface{}
      seq    uintptr

    }
    ````

  • 初始化斷續器
    go var ticker = time.NewTicker(time.Second)
  • 取消斷續器
    go var ticker = time.NewTicker(time.Second) ticker.Stop()

實例一:使用Ticker(並使用時間控制ticker)

  • 代碼如下:
    ```go
    package main
    import (
    "fmt"
    "time"
    )

    func TickerTest() *time.Ticker {
    // 創建一個斷續器
    var ticker = time.NewTicker(time.Second)

      go func() {
          // 使用for + range組合處理斷續器
          for t := range ticker.C {
              fmt.Println("tick at", t)
          }
      }()
    
      return ticker

    }

    func main() {
    ticker := TickerTest()
    time.Sleep(time.Second * 10)
    ticker.Stop()
    }
    ```

實例二:使用channel控制ticker

  • 參考鏈接:https://blog.csdn.net/yjp19871013/article/details/82048944

  • 代碼如下:
    ```go
    package main

    import (
    "fmt"
    "time"
    )

    func DoTicker(ticker *time.Ticker) chan<- bool {
    stopChan := make(chan bool)

      go func(ticker *time.Ticker) {
          // 註冊停止ticker方法
          defer ticker.Stop()
          for {
              select {
              // 處理斷續器事件
              case t := <-ticker.C:
                  fmt.Println("tick at", t)
              // 接受外部停止斷續器事件
              case stop := <-stopChan:
                  if stop {
                      fmt.Println("DoTicker Exit")
                      return
                  }
              }
          }
      }(ticker)
    
      // 返回由外部控制Ticker停止的Channel
      return stopChan

    }

    func main() {

      var ticker = time.NewTicker(time.Second)
      stopChan := DoTicker(ticker)
    
      time.Sleep(time.Second * 10)
      // 停止斷續器
      stopChan <- true
      time.Sleep(time.Second)     defer func() {
          ticker.Stop()
          fmt.Println("ticker stopped")
      } ()
      close(stopChan)

    }
    ```

實例三:使用channel控制停止ticker

  • 參考鏈接:https://www.kancloud.cn/digest/batu-go/153534

  • 代碼如下:
    ```go
    package main

    import (
    "fmt"
    "time"
    )

    func DoTicker(ticker *time.Ticker, times int) {
    // 創建有times個緩沖的byte通道
    stopChan := make(chan byte, times)

      go func(ticker *time.Ticker) {
    
          defer func() {
              // 經過調試,defer語句塊並未執行
              ticker.Stop()
              fmt.Println("ticker stopped")
          } ()
    
          for t := range ticker.C {
              fmt.Println("write stop channel")
    
              // 寫滿times次後,當前goroutine自動退出
              stopChan <- 0
              fmt.Println("tick at", t)
          }
    
          // 經調試,該語句並未執行
          fmt.Println("DoTicker1 Exit")
      }(ticker)

    }

    func main() {
    var ticker = time.NewTicker(time.Second)

      DoTicker(ticker, 5)
      time.Sleep(time.Second * 10)

    }
    ```

  • 調試輸出:
    ```go
    write stop channel
    tick at 2019-03-13 11:44:35.932692894 +0800 CST m=+1.000442776
    write stop channel
    tick at 2019-03-13 11:44:36.932643384 +0800 CST m=+2.000393270
    write stop channel
    tick at 2019-03-13 11:44:37.932565147 +0800 CST m=+3.000315031
    write stop channel
    tick at 2019-03-13 11:44:38.932735589 +0800 CST m=+4.000485469
    write stop channel
    tick at 2019-03-13 11:44:39.932553565 +0800 CST m=+5.000303443
    write stop channel

    Process finished with exit code 0
    ```

Golang定時器斷續器