在日常开发中,我们可能会遇到需要延迟执行或周期性地执行一些任务。这个时候就需要用到 Go
语言中的定时器。
在 Go
语言中,定时器类型有两种:time.Timer
一次性定时器和 time.Ticker
周期性定时器。本文将会对这两种定时器类型进行介绍。
准备好了吗?准备一杯你最喜欢的咖啡或茶,随着本文一探究竟吧。
Timer
是一个一次性的定时器,用于在未来的某一时刻执行一次操作。
创建 Timer
定时器的方式有两种:
NewTimer(d Duration) *Timer
:该函数接受一个 time.Duration
类型的参数 d
(时间间隔),表示定时器在过期之前等待的时间。NewTimer
返回一个新的 Timer
定时器,这个定时器在其内部维护一个通道 C
,该通道在定时器被触发时会接收当前的时间值。AfterFunc(d Duration, f func()) *Timer
:接受一个指定的时间间隔 d
和回调函数 f
。该函数返回一个新的 Timer
定时器,在定时器到期时直接调用 f
,而不是通过通道 C
发送信号。调用 Timer
的 Stop
方法可以停止定时器和取消调用 f
。下面的代码展示了如何使用 NewTimer
和 AfterFunc
来创建定时器以及定时器的基本用法:
// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/go/timer/timer/usage.go
package main
import (
"fmt"
"time"
)
func main() {
// 使用 NewTimer 创建一个定时器
timer := time.NewTimer(time.Second)
go func() {
select {
case <-timer.C:
fmt.Println("timer 定时器触发啦!")
}
}()
// 使用 AfterFunc 创建另一个定时器
time.AfterFunc(time.Second, func() {
fmt.Println("timer2 定时器触发啦!")
})
// 主goroutine等待两秒,确保看到定时器触发的输出
time.Sleep(time.Second * 2)
}
代码运行结果如下所示:
timer 定时器触发啦!
timer2 定时器触发啦!
下面是代码的逐步解析:
NewTimer
创建了一个定时器,然后在一个新的 goroutine
中监听它的 C
属性以等待定时器触发。AfterFunc
创建另一个定时器,通过指定一个 回调函数 来处理定时器到期事件。goroutine
等待足够长的时间以确保定时器的触发信息能够被打印出来。Reset(d Duration) bool
:该方法用于重置 Timer
定时器的过期时间,也可以理解为重新激活定时器。它接受一个 time.Duration
类型的参数 d
,表示定时器在过期之前等待的时间。
除此之外,该方法还返回一个 bool
值:
true
。false
(false
并不意味着激活定时器失败,只是标识定时器的当前状态)。下面是代码示例:
// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/go/timer/timer/reset.go
package main
import (
"fmt"
"time"
)
func main() {
timer := time.NewTimer(5 * time.Second)
// 第一次重置,定时器处于激活状态,因此返回 true
b := timer.Reset(1 * time.Second)
fmt.Println(b) // true
second := time.Now().Second()
select {
case t := <-timer.C:
fmt.Println(t.Second() - second) // 1s
}
// 第二次重置,定时器已经处于过期状态,因此返回 false
b = timer.Reset(2 * time.Second)
fmt.Println(b) // false
second = time.Now().Second()
select {
case t := <-timer.C:
fmt.Println(t.Second() - second) // 2s
}
}
代码运行结果如下所示:
true
1
false
2
下面是代码的逐步解析:
Reset
方法立即将其重置为 1 秒后到期。因为此时定时器仍处于激活状态(即还未到期),所以 Reset
方法返回 true
。select
语句等待定时器到期,并打印出实际经过的秒数(约等于 1 秒)。Reset
方法返回 false
。select
语句等待定时器到期,并打印出这次经过的秒数(约等于 2 秒)。Stop() bool
:该方法用于停止定时器。如果定时器停止成功,返回 true
,如果定时器已经过期或被停止,则返回 false
。切记:Stop
操作不会关闭通道 C
。
下面是代码示例:
// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/go/timer/timer/stop.go
package main
import (
"fmt"
"time"
)
func main() {
timer := time.NewTimer(3 * time.Second)
// 停止定时器,在定时器触发之前停止它,因此返回 true
stop := timer.Stop()
fmt.Println(stop) // true
stop = timer.Stop()
// 第二次停止定时器,此时定时器已经被停止了,返回 false
fmt.Println(stop) // false
}
代码运行结果如下所示:
true
false
下面是代码的逐步解析:
Stop
方法停止定时器。因为此时定时器还未触发,所以 Stop
返回 true
。Stop
方法尝试停止同一个定时器。由于定时器已经被停止,这次 Stop
返回 false
。Tciker
是一个周期性的定时器,用于在固定的时间间隔重复执行任务。它在每个间隔时间到来时,向其通道(Channel
)发送当前时间。
我们可以使用 NewTicker
函数来创建一个新的 Ticker
对象,该函数接受一个 time.Duration
类型的参数 d
(时间间隔)。
下面是代码示例:
// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/go/timer/ticker/usage.go
package main
import (
"context"
"fmt"
"time"
)
func main() {
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
timeout, cancelFunc := context.WithTimeout(context.Background(), time.Second*5)
defer cancelFunc()
go func() {
for {
select {
case <-timeout.Done():
fmt.Println("timeout done")
return
case <-ticker.C:
fmt.Println("定时器触发啦!")
}
}
}()
// 主goroutine等待 7 秒,确保看到定时器触发的输出
time.Sleep(time.Second * 7)
}
代码运行结果如下所示:
定时器触发啦!
定时器触发啦!
定时器触发啦!
定时器触发啦!
定时器触发啦!
timeout done
下面是代码的逐步解析:
defer ticker.Stop()
cancelFunc
被用于在退出前清理上下文。goroutine
中,select
语句用于监听两个通道:定时器的通道 (ticker.C
) 和超时上下文的完成通道 (timeout.Done()
)。当定时器每秒触发时,会打印出消息。当上下文超时(即 5 秒过后),打印出超时信息,并返回从而结束该 goroutine
。goroutine
通过 time.Sleep(time.Second * 7)
等待 7 秒,以确保能够观察到定时器触发和超时事件的输出。除了使用 select
语句监听 ticker.C
以外,我们还可以使用 for range
的形式进行监听:
for range ticker.C {}
需要注意的是,即使通过 Stop
方法停止 Ticker
定时器,其 C
通道不会被关闭。这意味着无论是通过 for select
还是 for range
去监听 ticker.C
,我们需要使用其他机制来退出循环,例如使用 context
上下文。
Reset(d Duration)
方法用于停止计时器并将其周期重置为指定的时间。下一个时间刻度将在新周期结束后生效。它接受一个 time.Duration
类型的参数 d
,表示新的周期。该参数必须大于零;否则 Reset
方法内部将会 panic
。
下面是代码示例:
// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/go/timer/ticker/reset.go
package main
import (
"time"
)
func main() {
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()
// 重置定时器
ticker.Reset(1 * time.Second)
second := time.Now().Second()
for t := range ticker.C {
// 1s
fmt.Printf("周期:%d 秒", t.Second()-second)
break
}
}
代码运行结果如下所示:
周期:1 秒
下面是代码的逐步解析:
time.Ticker
。Reset
方法重置定时器的触发间隔。5 秒变成 1 秒。Stop()
方法用于停止定时器。在 Stop
之后,将不再发送更多的 tick
给其通道 C
。切记:Stop
操作不会关闭通道 C
。
下面是代码示例:
// https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/go/timer/ticker/stop.go
package main
import (
"fmt"
"time"
)
func main() {
ticker := time.NewTicker(time.Second)
quit := make(chan struct{}) // 创建一个退出通道
go func() {
for {
select {
case <-ticker.C:
fmt.Println("定时器触发啦!")
case <-quit:
fmt.Println("协程停止啦!")
return // 接收到退出信号,退出循环
}
}
}()
time.Sleep(time.Second * 3)
ticker.Stop() // 停止定时器
close(quit) // 发送退出信号
fmt.Println("定时器停止啦!")
}
代码运行结果如下所示:
定时器触发啦!
定时器触发啦!
定时器触发啦!
协程停止啦!
定时器停止啦!
time.Ticker
对象。同时,引入了一个类型为 chan struct{}
的退出通道 quit。这个通道将用于向运行中的 goroutine
发送停止信号。goroutine
。在这个 goroutine
中,使用 for-select
循环来监听两个事件:定时器的触发(case <-ticker.C
)和退出信号(case <-quit
)。每当定时器触发时,它会打印一条消息。如果收到退出信号,它会打印一条消息并退出循环。goroutine
中,time.Sleep(time.Second * 3)
模拟了一段等待时间(3 秒),在这期间定时器会触发几次。goroutine
通过调用 Stop
方法停止定时器,然后关闭退出通道。goroutine
接收到退出信号后打印出一条消息并退出循环。Stop
不会关闭其通道 C
,因此我们需要借助其他方式(例如退出信号)来清理资源。
用途:
Timer
用于单次延迟执行任务。Ticker
重复执行任务。行为特点:
Timer
在设定的延迟时间过后触发一次,发送一个时间值到其通道。Ticker
按照设定的间隔周期性地触发,反复发送时间值到其通道。可控性:
Timer
可以被重置(Reset
方法)和停止(Stop
方法)。Reset
用于改变 Timer
的触发时间。Ticker
可以被重置(Reset
方法)和停止(Stop
方法)。Reset
用于改变 Ticker
触发的时间间隔。结束操作:
Timer
的 Stop
方法用于阻止 Timer
触发,如果 Timer
已经触发,Stop
不会从其通道中删除已发送的时间值。Ticker
的 Stop
方法用于停止 Ticker
的周期性触发,一旦停止,它不会再向通道发送新的值。Timer
还是 Ticker
定时器,调用 Stop
方法之后,并不会关闭它们的 C
通道。如果有其他的 goroutine
在监听这个通道,为避免潜在的内存泄漏,需要手动结束该 goroutine
。通常,这种资源释放的问题可以通过使用 context
或通过关闭信号(利用 Channel
实现)来解决。Ticker
定时器完成其任务后,为了防止内存泄漏,应调用 Stop
方法来释放相关资源。如果未及时停止 Ticker
,可能导致资源持续占用。本文深入探讨了 Go
语言中的 Timer
和 Ticker
定时器,详细介绍了它们的创建方式、基本用法以及相关的方法等。此外,文章还概括了这两个定时器之间的主要区别,并强调了在使用过程中的注意事项。
在编写 Go
代码时,我们应根据不同的应用场景去选择合适的定时器。同时,我们应遵循良好的规范,特别是在定时器使用完毕后及时释放资源,对于避免潜在的内存泄漏问题尤为重要。
文章里的所有代码已上传至:https://github.com/chenmingyong0423/blog/blob/master/tutorial-code/go/timer
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。