• 简介
  • 使用场景
    • 简单定时任务
    • 定时聚合任务
  • Ticker对外接口
    • 创建定时器
    • 停止定时器
  • 简单接口
  • 错误示例
  • 总结

    简介

    Ticker是周期性定时器,即周期性的触发一个事件,通过Ticker本身提供的管道将事件传递出去。

    Ticker的数据结构与Timer完全一致:

    1. type Ticker struct {
    2. C <-chan Time
    3. r runtimeTimer
    4. }

    Ticker对外仅暴露一个channel,指定的时间到来时就往该channel中写入系统时间,也即一个事件。

    在创建Ticker时会指定一个时间,作为事件触发的周期。这也是Ticker与Timer的最主要的区别。

    另外,ticker的英文原意是钟表的”滴哒”声,钟表周期性的产生”滴哒”声,也即周期性的产生事件。

    使用场景

    简单定时任务

    有时,我们希望定时执行一个任务,这时就可以使用ticker来完成。

    下面代码演示,每隔1s记录一次日志:

    1. // TickerDemo 用于演示ticker基础用法
    2. func TickerDemo() {
    3. ticker := time.NewTicker(1 * time.Second)
    4. defer ticker.Stop()
    5. for range ticker.C {
    6. log.Println("Ticker tick.")
    7. }
    8. }

    上述代码中,for range ticker.C会持续从管道中获取事件,收到事件后打印一行日志,如果管道中没有数据会阻塞等待事件,由于ticker会周期性的向管道中写入事件,所以上述程序会周期性的打印日志。

    定时聚合任务

    有时,我们希望把一些任务打包进行批量处理。比如,公交车发车场景:

    • 公交车每隔5分钟发一班,不管是否已坐满乘客;
    • 已坐满乘客情况下,不足5分钟也发车;

    下面代码演示公交车发车场景:

    1. // TickerLaunch用于演示ticker聚合任务用法
    2. func TickerLaunch() {
    3. ticker := time.NewTicker(5 * time.Minute)
    4. maxPassenger := 30 // 每车最大装载人数
    5. passengers := make([]string, 0, maxPassenger)
    6. for {
    7. passenger := GetNewPassenger() // 获取一个新乘客
    8. if passenger != "" {
    9. passengers = append(passengers, passenger)
    10. } else {
    11. time.Sleep(1 * time.Second)
    12. }
    13. select {
    14. case <- ticker.C: // 时间到,发车
    15. Launch(passengers)
    16. passengers = []string{}
    17. default:
    18. if len(passengers) >= maxPassenger { // 时间没到,车已座满,发车
    19. Launch(passengers)
    20. passengers = []string{}
    21. }
    22. }
    23. }
    24. }

    上面代码中for循环负责接待乘客上车,并决定是否要发车。每当乘客上车,select语句会先判断ticker.C中是否有数据,有数据则代表发车时间已到,如果没有数据,则判断车是否已坐满,坐满后仍然发车。

    Ticker对外接口

    创建定时器

    使用NewTicker方法就可以创建一个周期性定时器,函数原型如下:

    1. func NewTicker(d Duration) *Ticker

    其中参数d即为定时器事件触发的周期。

    停止定时器

    使用定时器对外暴露的Stop方法就可以停掉一个周期性定时器,函数原型如下:

    1. func (t *Ticker) Stop()

    需要注意的是,该方法会停止计时,意味着不会向定时器的管道中写入事件,但管道并不会被关闭。管道在使用完成后,生命周期结束后会自动释放。

    Ticker在使用完后务必要释放,否则会产生资源泄露,进而会持续消耗CPU资源,最后会把CPU耗尽。更详细的信息,后面我们研究Ticker实现原理时再详细分析。

    简单接口

    部分场景下,我们启动一个定时器并且永远不会停止,比如定时轮询任务,此时可以使用一个简单的Tick函数来获取定时器的管道,函数原型如下:

    1. func Tick(d Duration) <-chan Time

    这个函数内部实际还是创建一个Ticker,但并不会返回出来,所以没有手段来停止该Ticker。所以,一定要考虑具体的使用场景。

    错误示例

    Ticker用于for循环时,很容易出现意想不到的资源泄露问题,下面代码演示了一个泄露问题:

    1. func WrongTicker() {
    2. for {
    3. select {
    4. case <-time.Tick(1 * time.Second):
    5. log.Printf("Resource leak!")
    6. }
    7. }
    8. }

    上面代码,select每次检测case语句时都会创建一个定时器,for循环又会不断的执行select语句,所以系统里会有越来越多的定时器不断的消耗CPU资源,最终CPU会被耗尽。

    总结

    Ticker相关内容总结如下:

    • 使用time.NewTicker()来创建一个定时器;
    • 使用Stop()来停止一个定时器;
    • 定时器使用完毕要释放,否则会产生资源泄露;

    赠人玫瑰手留余香,如果觉得不错请给个赞~

    本篇文章已归档到GitHub项目,求星~ 点我即达