在 Go 里有很多种定时器的使用方法,像常规的 Timer
、Ticker
对象,以及经常会看到的 time.After(d Duration)
和 time.Sleep(d Duration)
方法。以上这些定时器的使用方法都来自Golang 原生 time
包,使用 time
包可以用来执行一些定时任务或者是周期性的任务。
func NewTimer(d Duration) *Timer
func (t *Timer) Reset(d Duration) bool
func (t *Timer) Stop() bool
func After(d Duration) <-chan Time
func AfterFunc(d Duration, f func()) *Timer
//timer例子
func main() {
timer := time.NewTimer(3 * time.Second) //启动定时器,生产一个Timer对象
select {
case <-timer.C:
fmt.Println("3秒执行任务")
}
timer.Stop() // 不再使用了,结束它
}
//time.After例子
func main() {
tChannel := time.After(3 * time.Second) // 其内部其实是生成了一个Timer对象
select {
case <-tChannel:
fmt.Println("3秒执行任务")
}
}
func main() {
timer := time.NewTimer(3 * time.Second)
for {
timer.Reset(4 * time.Second) // 这样来复用 timer 和修改执行时间
select {
case <-timer.C:
fmt.Println("每隔4秒执行任务")
}
}
}
从上面可以看出来 Timer
允许再次被启用,而 time.After
返回的是一个 channel
,将不可复用。
而且需要注意的是 time.After
本质上是创建了一个新的 Timer
结构体,只不过暴露出去的是结构体里的 channel 字段而已。
这里的
Ticker
跟Timer
的不同之处,就在于Ticker
时间达到后不需要人为调用Reset
方法,会自动续期。
func NewTicker(d Duration) *Ticker
func Tick(d Duration) <-chan Time
func (t *Ticker) Stop()
func main() {
ticker := time.NewTicker(3 * time.Second)
for range ticker.C {
fmt.Print("每隔3秒执行任务")
}
ticker.Stop()
}
package main
import (
"fmt"
"time"
)
func main() {
// Ticker 包含一个通道字段C,每隔时间段 d 就向该通道发送当时系统时间。
// 它会调整时间间隔或者丢弃 tick 信息以适应反应慢的接收者。
// 如果d <= 0会触发panic。关闭该 Ticker 可以释放相关资源。
ticker := time.NewTicker(5 * time.Second)
// 一定要调用Stop(),回收资源
defer ticker.Stop()
go func(t *time.Ticker) {
for {
// 每5秒中从chan t.C 中读取一次
<-t.C
fmt.Println("Ticker:", time.Now().Format("2006-01-02 15:04:05"))
}
}(ticker)
time.Sleep(30 * time.Second)
fmt.Println("ok")
}
每隔5秒执行一次,30秒后随main线程结束。
package main
import (
"fmt"
"time"
)
func main() {
// NewTimer 创建一个 Timer,它会在最少过去时间段 d 后到期,向其自身的 C 字段发送当时的时间
timer := time.NewTimer(5 * time.Second)
fmt.Println("开始时间:", time.Now().Format("2006-01-02 15:04:05"))
go func(t *time.Timer) {
times := 0
for {
<-t.C
fmt.Println("timer", time.Now().Format("2006-01-02 15:04:05"))
// 从t.C中获取数据,此时time.Timer定时器结束。如果想再次调用定时器,只能通过调用 Reset() 函数来执行
// Reset 使 t 重新开始计时,(本方法返回后再)等待时间段 d 过去后到期。
// 如果调用时 t 还在等待中会返回真;如果 t已经到期或者被停止了会返回假。
times++
// 调用 reset 重发数据到chan C
fmt.Println("调用 reset 重新设置一次timer定时器,并将时间修改为2秒")
t.Reset(2 * time.Second)
if times > 3 {
fmt.Println("调用 stop 停止定时器")
t.Stop()
}
}
}(timer)
time.Sleep(30 * time.Second)
fmt.Println("结束时间:", time.Now().Format("2006-01-02 15:04:05"))
fmt.Println("ok")
}
第一次执行时间为5秒以后。然后通过调用 time.Reset()
方法再次激活定时器,定时时间为2秒,最后通过调用 time.Stop()
把前面的定时器取消掉。
ticker
定时器表示每隔一段时间就执行一次,一般可执行多次。
timer
定时器表示在一段时间后执行,默认情况下只执行一次,如果想再次执行的话,每次都需要调用 time.Reset()
方法,此时效果类似ticker
定时器。同时也可以调用 Stop()
方法取消定时器
timer
定时器比 ticker
定时器多一个 Reset()
方法,两者都有 Stop()
方法,表示停止定时器,底层都调用了stopTimer()
函数。
除了上面的定时器外,Go 里的 time.Sleep
也起到了类似一次性使用的定时功能。只不过 time.Sleep
使用了系统调用。而像上面的定时器更多的是靠 Go 的调度行为来实现。
无论哪种计时器,.C
都是一个 chan Time
类型且容量为 1 的单向 Channel
,当有超过 1 个数据的时候便会被阻塞,以此保证不会被触发多次。