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.建立定时器ui

  • 接口定义.net

    func NewTimer(d Duration) *Timer
  • 使用简单实例调试

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

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

  • 接口定义接口

    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提示计时器在中止以前已过时,则应明确清空通道。
      go 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
相关文章
相关标签/搜索