相信有很多小伙伴和本人一样刚开始学习GO语言时在使用定时器的时候都会一些疑惑:
time.NewTicker() 和 time.Tick()的功能是一样的
他们有什么区别呢?
什么时候该用time.NewTicker()?
什么时候该用time.Tick()?

今天就带着大家来一探究竟。

使用方式

time.NewTicker()

ticker := time.NewTicker(time.Second)
for {
    select {
    case <- ticker.C:
        fmt.Println("wenjianjia")
    }
}

time.Tick():

for {
    select {
    case <- time.Tick(time.Second):
        fmt.Println("wenjianjia")
    }
}

以上两段代码是很常用的情况,看上去两种的用法是差不多的,只是time.NewTicker()是再for循环外提前声明的。

但!真的是这样吗?

源码解析

time.NewTicker()

func NewTicker(d Duration) *Ticker {
    if d <= 0 {
        panic(errors.New("non-positive interval for NewTicker"))
    }
    c := make(chan Time, 1)
    t := &Ticker{
        C: c,
        r: runtimeTimer{
            when:   when(d),
            period: int64(d),
            f:      sendTime,
            arg:    c,
        },
    }
    startTimer(&t.r)
    return t
}

time.Tick():

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

对比发现其实time.Tick只是对time.NewTicker的一个封装,底层其实还是调用了time.NewTicker(怪不得效果一样,恍然大悟···)。

接着我们结合time.Tick的实现和用法改进一下上面的写法:

for {
        select {
        // 暂不考虑异常情况
        //if d <= 0 {
        //    return nil
        //}
        case <-time.NewTicker(d).C:
            fmt.Println("wenjianjia")
        }
}

是不是发现问题了,如果在for循环里面使用time.Tick每次循环都是重新new一个Ticker出来,之前的也没有释放这不就造成泄露了吗。要是在一些需要长久循环的逻辑中使用了time.Tick后果不堪设想,这也是初学者比容容易犯的错误。

我们再来看下time.Tick的原文注释:

// Tick is a convenience wrapper for NewTicker providing access to the ticking
// channel only. While Tick is useful for clients that have no need to shut down
// the Ticker, be aware that without a way to shut it down the underlying
// Ticker cannot be recovered by the garbage collector; it "leaks".
// Unlike NewTicker, Tick will return nil if d <= 0.
func Tick(d Duration) <-chan Time {
    if d <= 0 {
        return nil
    }
    return NewTicker(d).C
}

其实time.Tick的注释也写得很清楚了,大致的意思是:Tick是NewTicker的封装,只提供对ticking频道的访问。虽然Tick对于不需要关闭Ticker的客户端很有用,但要注意,如果没有关闭Ticker的方法,垃圾收集器将无法恢复底层的Ticker;将会造成内存泄露。与NewTicker不同,如果d<=0,Tick将返回nil。

总结

说了这么多了总结下来就四点:

  1. time.Tick是time.NewTicker的一个封装
  2. time.Tick用起来轻便简单但是不会销毁自身对象,适合在一次性的逻辑中使用
  3. time.NewTicker和time.Tick相比虽然多了些许的繁琐,但是始终是一个对象循环使用时不会造成泄露问题
  4. 在for循环中必须使用time.NewTicker如果使用time.Tick会造成内存泄露