Golang定时器断续器

定时器

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
    
  • 使用简单实例

    var timer = NewTimer(time.Second)
    
    go func() {
        for {
            select {
            case <-timer.C:
                fmt.Println("time out.")
            }
        }
    }()
    
  • NewTimer源代码:

    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)
    
  • 代码实例

    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.停止定时器

  • 接口定义

    func (t *Timer) Stop() bool
    
    • 本接口可以防止计时器出触发。如果定时器停止,则返回true,如果定时器已过期或已停止,则返回false。
      Stop不关闭通道,以防止通道读取的操作不正确。

    • 为防止通过NewTimer创建的定时器,在调用Stop接口后触发,检查Stop返回值并清空通道。如:

      if !t.Stop() {
          <-t.C
      }
      

      但不能与Timer的通道中的其他接受同时进行!!!

    • 对于使用AfterFunc(d, f)创建的定时器,如果t.Stop()返回false,则定时器已经过期,并且函数f已经在自己的协程中启动。
      无论函数f是否执行完成,Stop()返回不会阻塞,如果用户需要知道f是否执行完毕,必须明确地与f协调。

  • 内部使用接口

    func stopTimer(*runtimeTimer) bool
    
  • 代码实例

    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.重置定时器

  • 接口定义

    func (t *Timer) Reset(d Duration) bool
    
    • 定时器被激活,返回true,若定时器已过期或已被停止,返回false。

    • 若程序已从t.C中接受数据,定时器已知过期,t.Rest()可直接使用

    • 若程序还尚未从t.C收到一个值,则必须停止定时器。如果Stop提示计时器在停止之前已过期,则应明确清空通道。

      if !t.Stop() {
          <-t.c
      }
      t.Rest(d)
      
  • 代码实例

    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
                }defer func() {
    		ticker.Stop()
    		fmt.Println("ticker stopped")
    	} ()
            }
        }(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接口

  • 接口定义

    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接口

  • 接口定义

    func AfterFunc(d Duration, f func()) *Timer
    
    • 等待一段时间d后,Golang会在自己的协程中调用f。并返回一个定时器,可以使用Stop方法取消调用
  • 代码实例

    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:经过取消的断续器,不能再复用,需要重新创建一个新的断续器。

  • 结构定义如下:

    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
    }
    
  • 初始化断续器

    var ticker = time.NewTicker(time.Second)
    
  • 取消断续器

    var ticker = time.NewTicker(time.Second)
    ticker.Stop()
    

实例一:使用Ticker(并使用时间控制ticker)

  • 代码如下:

    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

  • 代码如下:

    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)	
        close(stopChan)
    }
    

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

  • 参考链接:https://www.kancloud.cn/digest/batu-go/153534

  • 代码如下:

    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)
    }
    
  • 调试输出:

    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
    
posted @ 2019-03-14 13:48  waynezly  阅读(492)  评论(0编辑  收藏  举报