在进行并发编程时,有时候会需要定时功能,比如监控某个GO程是否会运行过长时间、定时打印日志等等。
GO标准库中的定时器主要有两种,一种为Timer定时器,一种为Ticker定时器。Timer计时器使用一次后,就失效了,需要Reset()才能再次生效。而Ticker计时器会一直生效,接下来分别对两种进行介绍。
1. Timer定时器timerproc()fsendtime()
接着看看Timer的结构:
type Timer struct {
C <-chan Time
// contains filtered or unexported fields
}
time.NewTimertime.AfterFunctime.Afte
1.1 time.NewTimer() 和 time.After()
1.1.1 time.NewTimer()
查看以下简单的应用代码:
package main
import (
"fmt"
"time"
)
type H struct {
t *time.Timer
}
func main() {
fmt.Println("main")
h:=H{t: time.NewTimer(1*time.Second)}
go h.timer()
time.Sleep(10*time.Second)
}
func (h *H) timer() {
for {
select {
case <-h.t.C:
fmt.Println("timer")
}
}
}
我们创建了一个timer,设置时间为1S。然后使用一个select对timer的C进行接受,GO程运行timer()函数,会一直阻塞直到超时发生(接收到C的数据),此时打印timer。
Stop() 停止 Timer
func (t *Timer) Stop() bool
Stop()Stop()
实际上,调用此方法后,此Timer会被从时间堆中移除。
Reset()重置Timer
注意,Timer定时器超时一次后就不会再次运行,所以需要调用Reset函数进行重置。修改select中代码,在Case中添加一个重置的代码:
select {
case <-h.t.C:
fmt.Println("timer")
h.t.Reset(1*time.Second)
}
可以看到,会不停的打印timer,这是因为使用了Reset函数重置定时器。
注意!不能随意的对Reset方法进行调用,官网文档中特意强调:
For a Timer created with NewTimer, Reset should be invoked only on stopped or expired timers with drained channels.
大概的意思就是说,除非Timer已经被停止或者超时了,否则不要调用Reset方法,因为,如果这个 Timer 还没超时,不先去Stop它,而是直接Reset,那么旧的 Timer 仍然存在,并且仍可能会触发,会产生一些意料之外的事。所以通常使用如下的代码,安全的重置一个不知状态的Timer(以上的代码中,Reset调用时,总是处于超时状态):
if !t.Stop() {
select {
case <-h.t.C:
default:
}
}
h.t.Reset(1*time.Second)
1.1.2 time.After()
time.After()
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("main")
go ticker()
time.Sleep(100 * time.Second)
}
func ticker() {
for {
select {
case <-time.After(1 * time.Second):
fmt.Println("timer")
}
}
}
注意,此方法虽然简单,但是没有Reset方法来重置定时器,但是可以搭配for 和select的重复调用来模拟重置。
1.1.3 sendtime函数
NewTimerAfterTimersendTime
1.2 time.AfterFunc
此方法可以接受一个func类型参数,在计时结束后,会运行此函数,查看以下代码,猜猜会出现什么结果?
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("main")
t := time.AfterFunc(1*time.Second, func() {
fmt.Println("timer")
})
go timer(t)
time.Sleep(10 * time.Second)
}
func timer(t *time.Timer) {
select {
case <-t.C:
fmt.Println("123")
}
}
结果只打印了main以及timer。这是因为此方法并不会调用上文提到的sendtime()函数,即不会发送值给Timer的Channel,所以select就会一直阻塞。
f函数
AfterFuncNewTimerAfterTimergoroutinef
ftimerprocgoroutine
2. Ticker定时器
Ticker
time.NewTicker
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("main")
t:=time.NewTicker(1*time.Second)
go timer(t)
time.Sleep(10 * time.Second)
}
func timer(t *time.Ticker) {
for{
select {
case <-t.C:
fmt.Println("timer")
}
}
}