一:简介
在日常开发中,我们可能会遇到需要延迟执行或周期性地执行一些任务。这个时候就需要用到 go 语言中的定时器。
在 go 语言中,定时器类型有两种:一次性定时器time.timer 和 周期性定时器time.ticker 。本文将会对这两种定时器类型进行介绍。
二、timer:一次性定时器
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 来创建定时器以及定时器的基本用法:
package main
import (
"fmt"
"time"
)
func main() {
// 使用 newtimer 创建一个定时器,1s后往timer.c中发送当前时间
timer := time.newtimer(time.second)
gofunc() {
select {
case <-timer.c:
fmt.println("timer 定时器触发啦!")
}
}()
// 使用 afterfunc 创建另一个定时器,1s后执行func
time.afterfunc(time.second, func() {
fmt.println("timer2 定时器触发啦!")
})
// 主goroutine等待两秒,确保看到定时器触发的输出
time.sleep(time.second * 2)
}代码运行结果如下所示:
timer 定时器触发啦!
timer2 定时器触发啦!
下面是代码的逐步解析:
- 首先使用
newtimer创建了一个定时器,然后在一个新的goroutine中监听它的c属性以等待定时器触发。 - 其次,使用
afterfunc创建另一个定时器,通过指定一个 回调函数 来处理定时器到期事件。 - 最后,主
goroutine等待足够长的时间以确保定时器的触发信息能够被打印出来。
方法详解
resetreset(d duration) bool:该方法用于重置 timer 定时器的过期时间,也可以理解为重新激活定时器。它接受一个 time.duration 类型的参数 d,表示定时器在过期之前等待的时间。
除此之外,该方法还返回一个 bool 值:
- 如果定时器处于活动的状态,返回
true。 - 如果定时器已经过期或被停止了,返回
false(false并不意味着激活定时器失败,只是标识定时器的当前状态)。
下面是代码示例:
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
下面是代码的逐步解析:
- 首先,创建了一个定时器,设置为
5秒后到期。 - 然后调用
reset方法立即将其重置为1秒后到期。因为此时定时器仍处于激活状态(即还未到期),所以reset方法返回true。 - 接下来的
select语句等待定时器到期,并打印出实际经过的秒数(约等于1秒)。 - 接着第二次重置定时器,这次设置为
2秒后到期。由于定时器在这次重置时已经到期,reset方法返回false。 - 最后,再次使用
select语句等待定时器到期,并打印出这次经过的秒数(约等于2秒)。
stop
stop() bool:该方法用于停止定时器。如果定时器停止成功,返回 true,如果定时器已经过期或被已经被停止过,则返回 false。切记:stop 操作不会关闭通道 c。这意味着无论是通过 for select 还是 for range 去监听 ticker.c,我们需要使用其他机制来退出循环,例如使用 context 上下文。下面是代码示例:
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
下面是代码的逐步解析:
- 首先,创建了一个设置为
3秒后触发的定时器。 - 然后立即调用
stop方法停止定时器。因为此时定时器还未触发,所以stop返回true。 - 最后再次调用
stop方法尝试停止同一个定时器。由于定时器已经被停止,这次stop返回false。
三:ticker:周期性定时器
tciker 是一个周期性的定时器,用于在固定的时间间隔重复执行任务。它在每个间隔时间到来时,向其通道(channel)发送当前时间。
基本使用
我们可以使用 newticker 函数来创建一个新的 ticker 对象,该函数接受一个 time.duration 类型的参数 d(时间间隔)。
下面是代码示例:
package main
import (
"context"
"fmt"
"time"
)
func main() {
// 每隔1秒往ticker.c中发送当前时间
ticker := time.newticker(time.second)
defer ticker.stop()
// 使用context控制下面的for select退出
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() - 然后,创建一个在
5秒后超时的上下文。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 上下文。
方法详解
resetreset(d duration) 方法用于停止计时器并将其周期重置为指定的时间。下一个时间刻度将在新周期结束后生效。它接受一个 time.duration 类型的参数 d,表示新的周期。该参数必须大于零;否则 reset 方法内部将会 panic。
下面是代码示例:
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 秒
下面是代码的逐步解析:
- 首先,创建了一个每
5秒触发一次的定时器time.ticker。 - 其次,使用
reset方法重置定时器的触发间隔。5秒变成1秒。 - 最后,通过一次循环,打印定时器的周期,预期结果为
1秒。
stopstop() 方法用于停止定时器。在 stop 之后,将不再发送更多的 tick 给其通道 c。切记:stop 操作不会关闭通道 c。
下面是代码示例:
package main
import (
"fmt"
"time"
)
func main() {
ticker := time.newticker(time.second)
quit := make(chanstruct{}) // 创建一个退出通道
go func() {
for {
select {
// ticker stop后,不会往ticker.c中发送当前时间了,并且ticker.c并没有关闭,所以stop后,不会再走该case了
case <-ticker.c:
fmt.println("定时器触发啦!")
// 关闭的chan是可以读的,读完chan中已有数据后,可以一直读出对应类型的零值,所以一旦quit被close,quit立马可读
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在设定的延迟时间过后触发一次,发送一个时间值到其通道。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代码时,我们应根据不同的应用场景去选择合适的定时器。同时,我们应遵循良好的规范,特别是在定时器使用完毕后及时释放资源,对于避免潜在的内存泄漏问题尤为重要。
到此这篇关于go中的timer 和 ticker的文章就介绍到这了,更多相关go timer 和 ticker内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论