Golang中有两种类型的定时器:
Timer 用于延迟执行某个任务
Ticker 用于间隔执行某个任务。
不罗嗦了,我们直接开始看这两种定时器的具体定义和使用!
Timer结构体源码:
type Timer struct {
C <-chan Time
r runtimeTimer
}
C <-chan Time
通道上,除非Timer是由AfterFunc
方法创建的。(后文会介绍)NewTimer
或AfterFunc
方法创建。(后文会介绍)源码:
func (t *Timer) Stop() bool {
if t.r.f == nil {
panic("time: Stop called on uninitialized Timer")
}
return stopTimer(&t.r)
}
Stop方法用于停止计时器计时,从而防止定时器触发。
if !t.Stop() { // 如果Stop失败
<-t.C // 排空通道
}
AfterFunc(d, f)
创建的计时器,如果 t.Stop
返回 false,则计时器已经到期,并且函数 f 已在其自己的 goroutine 中启动; Stop 不会等待 f 完成才返回。如果调用者需要知道 f 是否完成,它必须显式地与 f 协调。源码
func (t *Timer) Reset(d Duration) bool {
if t.r.f == nil {
panic("time: Reset called on uninitialized Timer")
}
w := when(d)
return resetTimer(&t.r, w)
}
Reset方法将定时器的过期时间设置为新的,即当前时间过d Duration
时间后.
func main() {
timer := time.NewTimer(time.Second * 5) // 计时5秒
time.Sleep(time.Second)
log.Println(timer.Stop()) // 一秒后停止计时 :true
time.Sleep(time.Second)
log.Println(timer.Reset(time.Second * 5)) // 再过一秒后Reset成5秒:false
log.Println(<-timer.C) // 输出的是重新Reset,5秒后的时间。
}
可以看到,Reset时由于计时器已经被Stop,返回了false。实际上还是设置了新的过期时间为5秒。
请注意,不可能正确使用 Reset 的返回值,因为在耗尽通道和新计时器到期之间存在竞争条件。如上所述,应始终在已停止或过期的通道上调用重置。返回值的存在是为了保持与现有程序的兼容性。
NewTimer
方法创建的Timer,调用Reset方法时,应该确保Timer是一个通道中没有值的已过期或已停止的计时器。如果通道不为空,接收方可能会收到两个结果:一个原本的,一个新的。if !t.Stop() {
<-t.C
}
t.Reset(d)
AfterFunc(d, f)
创建的计时器,Reset 要么重新安排 f 运行的时间(在这种情况下 Reset 返回 true),要么安排 f 再次运行(在这种情况下返回 false)。当 Reset 返回 false 时,Reset 既不会等待前一个 f 完成才返回,也不保证后续运行 f 的 goroutine 不会与前一个 goroutine 并发运行。如果调用者需要知道 f 的先前执行是否完成,它必须显式地与 f 协调。前面也说了, Timer必须由NewTimer
或AfterFunc
方法创建。现在就来看看怎么使用这两个方法。
方法源码:
func NewTimer(d Duration) *Timer {
c := make(chan Time, 1)
t := &Timer{
C: c,
r: runtimeTimer{
when: when(d),
f: sendTime,
arg: c,
},
}
startTimer(&t.r)
return t
}
NewTimer
返回一个Timer,在经过指定的时间间隔d Duration
后,Timer会将当前时间发送到其C通道上。方法源码:
func AfterFunc(d Duration, f func()) *Timer {
t := &Timer{
r: runtimeTimer{
when: when(d),
f: goFunc,
arg: f,
},
}
startTimer(&t.r)
return t
}
AfterFunc
方法在指定的时间间隔d Duration
后,在自己的goroutine中自动调用指定的方法f func()
。AfterFunc
方法返回的Timer可以用于取消调用(使用Stop方法)。AfterFunc
方法返回的Timer的C通道是nil的。除了NewTimer
和AfterFunc
方法外,After
方法可以创建定时器,但是After方法实际上是通过调用NewTimer
方法创建的。看源码:
func After(d Duration) <-chan Time {
return NewTimer(d).C
}
从源码可以看到,After
方法调用NewTime
r方法创建了一个Timer,但是只返回了Timer的C通道。
这意味着在指定时间到达之前,调用者没有无法通过调用
Timer.Stop
方法来停止计时,垃圾收集器不会回收底层计时器。
所以如果担心效率,请改用 NewTimer,并在不再需要计时器时调用 Timer.Stop
场景:在指定时间间隔后,开始执行指定任务。
func main() {
// 创建一个5秒的定时器
timer := time.NewTimer(time.Second * 5)
// 起一个协程,5秒后执行domSomeThing方法
go func() {
<-timer.C
doSomeThing()
}()
// 主线程继续执行其他的
// 这里睡眠等待子协程输出
time.Sleep(time.Second * 6)
}
func doSomeThing() {
log.Println("DO")
}
上面的示例也可以将NewTimer
替换成使用After
方法:
func main() {
// 创建一个5秒的定时器
c := time.After(time.Second * 5)
// 起一个协程,5秒后执行domSomeThing方法
go func() {
<-c
doSomeThing()
}()
// 主线程继续执行其他的
// 这里睡眠等待子协程输出
time.Sleep(time.Second * 6)
}
func doSomeThing() {
log.Println("DO")
}
当然,这里使用AfterFunc方法
好像更加优雅
func main() {
// AfterFunc方法将在5秒后执行doSomeThing方法
_ = time.AfterFunc(time.Second*5, doSomeThing)
// 主线程继续执行其他的
// 这里睡眠等待子协程输出
time.Sleep(time.Second * 6)
}
func doSomeThing() {
log.Println("DO")
}
场景:执行一个比较耗时的操作doSomeThing,如果执行耗时超过5秒,就打印超时日志。
func main() {
// 定时器设置超时时间为4秒
timer := time.NewTimer(time.Second * 4)
// resultChan 用以接收返回结果
resultChan := make(chan int, 1)
// 起一个协程去执行耗时操作
go func() {
resultChan <- doSomeThing() // 执行结果放入结果通道
}()
// 监听两个通道
select {
case <-timer.C:
log.Println("doSomeThing timeout.")
case r := <-resultChan:
log.Printf("doSomeThing got result:%d\n", r)
}
// 这里等2秒,等待子协程输出
time.Sleep(2 * time.Second)
}
func doSomeThing() int {
log.Println("doSomeThing start.")
time.Sleep(5 * time.Second) // 模拟耗时操作
log.Println("doSomeThing end.")
return 100
}
输出结果:
2024/07/24 09:49:07 doSomeThing start.
2024/07/24 09:49:11 doSomeThing timeout.
2024/07/24 09:49:12 doSomeThing end.
Ticker源码:
type Ticker struct {
C <-chan Time // The channel on which the ticks are delivered.
r runtimeTimer
}
// sendTime does a non-blocking send of the current time on c.
func sendTime(c any, seq uintptr) {
select {
case c.(chan Time) <- Now():
default:
}
}
func (t *Ticker) Stop() {
stopTimer(&t.r)
}
func (t *Ticker) Reset(d Duration) {
if d <= 0 {
panic("non-positive interval for Ticker.Reset")
}
if t.r.f == nil {
panic("time: Reset called on uninitialized Ticker")
}
modTimer(&t.r, when(d), int64(d), t.r.f, t.r.arg, t.r.seq)
}
创建Ticker只有NewTicker
一个方法:
func NewTicker(d Duration) *Ticker {
if d <= 0 {
panic("non-positive interval for NewTicker")
}
// Give the channel a 1-element time buffer.
// If the client falls behind while reading, we drop ticks
// on the floor until the client catches up.
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
}
场景:每过指定时间间隔,执行一次任务。
func main() {
// 指定时间间隔为5秒
ticker := time.NewTicker(time.Second * 5)
for {
<-ticker.C
// 每5秒执行一次
doSomeThing()
}
}
func doSomeThing() {
log.Println("doSomeThing start.")
time.Sleep(1 * time.Second) // 模拟耗时操作
log.Println("doSomeThing end.")
}