Go进阶之定时器Ticker

0 阅读2分钟

Ticker是周期性定时器.即周期性的触发一个事件.通过Ticker本身提供的管道将事件

传递出去.

Ticker数据结构:

源码位置:src/time/tick.go

type Ticker struct {
    C          <-chan Time // The channel on which the ticks are delivered.
    initTicker bool
}

Ticker对外仅暴露了一个channel.当指定时间到来时就往该channel中写入系统时

间.即一个事件.

1.简单定时任务:

func main() {
    ticker := time.NewTicker(1 * time.Second)

    defer ticker.Stop()

    for range ticker.C {
       log.Println("tick")
    }
}

执行结果:

定时聚合任务:

示例:

func TickerLaunch() {
    //5分钟执行一次.
    ticker := time.NewTicker(5 * time.Minute)

    defer ticker.Stop()
    maxPassenger := 30
    passengers := make([]string, 0, maxPassenger)
    for {
       passenger := GetNewPassenger()
       if passenger != "" {
          passengers = append(passengers, passenger)
       } else {
          time.Sleep(1 * time.Second)
       }
       select {
       case <-ticker.C:
          Launch(passengers)
          passengers = []string{}
       default:
          if len(passengers) >= maxPassenger {
             Launch(passengers)
             passengers = []string{}
          }
       }
    }
}

func Launch(passengers []string) {
    for i := range passengers {
       fmt.Println(passengers[i], "发车了")
    }
}

func GetNewPassenger() string {
    return "乘客"
}

2.Ticker对外接口:

1)创建定时器:

源码位置:src/time/tick.go

func NewTicker(d Duration) *Ticker {
    if d <= 0 {
       panic("non-positive interval for NewTicker")
    }
    // Give the channel a 1-element time buffer.
    // If the client falls behind while reading, we drop ticks
    // on the floor until the client catches up.
    c := make(chan Time, 1)
    t := (*Ticker)(unsafe.Pointer(newTimer(when(d), int64(d), sendTime, c, syncTimer(c))))
    t.C = c
    return t
}

2).停止定时器:

源码位置:src/time/tick.go

// Stop turns off a ticker. After Stop, no more ticks will be sent.
// Stop does not close the channel, to prevent a concurrent goroutine
// reading from the channel from seeing an erroneous "tick".
func (t *Ticker) Stop() {
    if !t.initTicker {
       // This is misuse, and the same for time.Timer would panic,
       // but this didn't always panic, and we keep it not panicking
       // to avoid breaking old programs. See issue 21874.
       return
    }
    stopTimer((*Timer)(unsafe.Pointer(t)))
}

流程图:

image.png

总流程图:

image.png

3.简单接口:

在有些场景下.启动一个定时器后.该定时器永远不会停止.比如定时轮询任务.此时可

以使用一个简单的Tick函数来获取定时器的管道.函数如下:

源码位置:src/runtime/tick.go

func Tick(d Duration) <-chan Time {
    if d <= 0 {
       return nil
    }
    return NewTicker(d).C
}

从源码可以看出其实是创建了一个Ticker.然后只返回了管道.并没有返回ticker.所以

没有办法停止.

4.错误示例:

当Ticker用于for循环时.很容易出现意想不到的资源泄漏问题.

示例:

func WrongTicker() {
    for  {
       select {
       case <-time.Tick(time.Second):
          log.Printf("resource leak!")
       }
    }
}

上面的代码.select每次检测case语句都会创建一个定时器.for循环又会不断的执行

select语句.系统里会有越来越多的定时器不断地消耗CPU资源.最终CPU资源被耗

尽.

如果大家喜欢我的分享的话.可以关注我的微信公众号

念何架构之路