一、前言

      golang 的time.Ticker一般用来作为时间周期执行任务的。

二、demo

     这个是官网贴出的例子,作用是每10s钟会输出当前的时间。

package main

import (
	"fmt"
	"time"
)

func main() {
	ticker := time.NewTicker(time.Second)
	defer ticker.Stop()
	done := make(chan bool)
	go func() {
		time.Sleep(10 * time.Second)
		done <- true
	}()
	for {
		select {
		case <-done:
			fmt.Println("Done!")
			return
		case t := <-ticker.C:
			fmt.Println("Current time: ", t)
		}
	}
}

三、解析

Gotime.Ticker
//Ticker保管一个通道,并每隔一段时间向其传递"tick"。
type Ticker struct {
	C <-chan Time // 有一个C的只读channel
	r runtimeTimer
}

type runtimeTimer struct {
	tb uintptr
	i  int

	when   int64
	period int64 // 标记是否为周期和对应的时间
	f      func(interface{}, uintptr) // 处理函数
	arg    interface{}
	seq    uintptr
}

在使用定时timer的时候都是通过 NewTimer 或 AfterFunc 函数来获取。

func NewTimer(d Duration) *Timer {
    c := make(chan Time, 1)
    t := &Timer{
        C: c,
        r: runtimeTimer{
            when: when(d), //表示达到时间段d时候调用f
            f:    sendTime,  // f表示一个函数调用,这里的sendTime表示d时间到达时向Timer.C发送当前的时间
            arg:  c,  // arg表示在调用f的时候把参数arg传递给f,c就是用来接受sendTime发送时间的
        },
    }
    startTimer(&t.r)
    return t
}

       定时器的具体实现逻辑,都在 runtime 中的 time.go 中,它的实现是通过四叉树堆(heep)实现的(runtimeTimer 结构中的i字段,表示在堆中的索引)。通过构建一个最小堆,保证最快拿到到期了的定时器执行。定时器的执行,在专门的 goroutine 中进行的:go timerproc()

这个时候我们再来分析一下上边的代码:

package main

import (
	"fmt"
	"time"
)

func main() {
    //NewTicker()返回一个新的Ticker,该Ticker包含一个通道字段,并会每隔时间段d就向该通道发送当时的时间。
    //它会调整时间间隔或者丢弃tick信息以适应反应慢的接收者。
    //如果d<=0会panic。关闭该Ticker可以释放相关资源。
	ticker := time.NewTicker(time.Second)

    //Stop()关闭一个Ticker。在关闭后,将不会发送更多的tick信息。Stop不会关闭通道t.C,以避免从该通道的读取不正确的成功
	defer ticker.Stop()
	done := make(chan bool)
	go func() {
		time.Sleep(10 * time.Second)
		done <- true
	}()
	for {
		select {
		case <-done:
			fmt.Println("Done!")
			return
		case t := <-ticker.C:
			fmt.Println("Current time: ", t)
		}
	}
}

NewTicker()返回一个新的Ticker,该Ticker包含一个通道字段,并会每隔时间段d就向该通道发送当时的时间。它会调整时间间隔或者丢弃tick信息以适应反应慢的接收者。如果d<=0会panic。关闭该Ticker可以释放相关资源。

Stop()关闭一个Ticker。在关闭后,将不会发送更多的tick信息。Stop不会关闭通道t.C,以避免从该通道的读取不正确的成功
 

四、注意事项

  • ticker 创建完之后,不是马上就有一个 tick,第一个 tick 在 x 秒(你自己写的)之后。
  • Go语言的定时器实质是单向通道,time.Timer结构体类型中有一个time.Time类型的单向chan。
  • time.NewTicker定时触发执行任务,当下一次执行到来而当前任务还没有执行结束时,会等待当前任务执行完毕后再执行下一次任务。查阅go官网的文档和经过代码验证。
  • t.Stop()在这里并不会停止定时器。这是因为Stop会停止Timer,停止后,Timer不会再被发送,但是Stop不会关闭通道,防止读取通道发生错误。如果想停止定时器,只能让go程序自动结束。

五、参考文章