一、前言
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程序自动结束。