首页 > 其他 > 详细

Golang定时器断续器

时间:2019-03-14 14:02:59      阅读:206      评论:0      收藏:0      [点我收藏+]

定时器

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定时器断续器

原文:https://www.cnblogs.com/wayne666/p/10529820.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!