golang time.Newtimer和time.NewTicker使用和不同

1. time.Newtimer是可以在没有强引用的时候被gc回收掉的。但是time.NewTicker必须在defer中使用stop来释放资源,否则资源永远不会被gc回收

2. time.Tick(d Duration) <-chan Time方法是存在资源泄漏的,见注释:

// 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.

这种只能用于no need to shut down的情况,因此一般是不应该使用这个的

相关区别:

1. timer是用于只执行一次延时获取能力的情况。如果要多次使用,需要结合reset。例如:

func TestCs2(t *testing.T) {
    timer := time.NewTimer(2 * time.Second)
    for {
       select {
       case t := <-timer.C:
          timer.Reset(2 * time.Second)
          fmt.Printf("time:%v
", t)
       }
    }
}

2. timer的注释中明确说明了如果使用reset的正确用法。

func TestBf(t *testing.T) {
    fmt.Printf("time:%v
", time.Now())
    timer := time.NewTimer(time.Second)
    time.Sleep(2 * time.Second)
    if !timer.Stop() {
       <-timer.C
    }
    timer.Reset(5 * time.Second)
    for {
       x :=
          <-timer.C
       fmt.Printf("time:%v
", x)
    }

}

step1 没有进行if !timer.Stop()是因为reset的值和创建时的值都是2s。因此无需reset。

其他变更了reset值的情况,必须进行if !timer.Stop()的判断。

3. time.ticker是重复进行定时触发的。和time.timer一样,其实底层是从一个1个值的channel中获取定时触发的time值。问题在于,time.ticker的stop方法是没有返回bool值的。因此没有办法通过timer类似的

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

这种方式,将已经放入到channel中的数据取出。

因此

func TestCs(t *testing.T) {
    fmt.Printf("time:%v
", time.Now())
    timer := time.NewTicker(time.Second)
    defer timer.stop()
    time.Sleep(2 * time.Second)
    go func() {
       for {
          select {
          case x := <-timer.C:
             fmt.Printf("time:%v
", x)
          }
       }
    }()
    timer.Reset(5 * time.Second)
    time.Sleep(time.Hour)
}

类似以上代码,会打印出如下结果

time:2024-01-21 21:04:24.857927 +0800 +08 m=+0.011576126
time:2024-01-21 21:04:25.858828 +0800 +08 m=+1.012470209
time:2024-01-21 21:04:31.8597 +0800 +08 m=+7.013304418
time:2024-01-21 21:04:36.859213 +0800 +08 m=+12.012786126

第二次21:04:25,就是没有取出已经放入在channl中的数据导致的。

同样注意,即使是timer。不先进行if !timer.Stop() { <-timer.C },而是直接操作timer.Reset(5 * time.Second)也是可以的,只不过会和ticker的情况一样,如果是channel中已经触发过了的数据,没有被取出,会在取出的时候,第一次间隔还是reset前的时间间隔。

time.After的情况和以上的情况类似,可以参见这篇文章

Go坑:time.After可能导致的内存泄露问题分析