在深入探讨 Go 1.23 版本对 Timer 和 Ticker 定时器进行的优化之前,以下是关于这两种定时器的基本介绍:
在 Go 1.23 之前,如果一个 Timer 或 Ticker 没有被显式调用 Stop 方法,即使程序不再引用它们,它们也不会立即被垃圾回收。Timer 会在触发后被回收,而 Ticker 则从来不会被自动回收,这可能导致内存泄漏。
Go 1.23 版本改进了这一点。如果程序不再引用一个 Timer 或 Ticker(即没有其他部分的代码持有它们的引用),即使没有调用 Stop 方法,它们也会有资格立即被垃圾回收。这减少了内存泄漏的风险。
在 Go 1.23 之前,与 Timer 或 Ticker 关联的通道带有一个元素缓冲区,这导致 Reset 或 Stop 方法在调用后,可能仍会接收到之前准备好的旧值。
Go 1.23 版本中,计时器通道变成了无缓冲的(容量为 0)。这意味着在调用 Reset 或 Stop 方法后,Go 可以保证不会再接收到旧的值,使得 Reset 和 Stop 的使用更加可靠。
由于通道现在是无缓冲的,len 和 cap 操作返回的值变成了 0,而不是 1。这可能会影响那些依赖轮询通道长度来判断是否能成功接收值的代码。
以下是在不同 Go 版本中的 Timer 行为的示例代码:
package main
import (
"fmt"
"time"
)
func main() {
// 程序退出信号
quit := make(chan bool)
timer := time.NewTimer(2 * time.Second)
go func() {
// 确保定时器已触发并发送信号
time.Sleep(4 * time.Second)
// 试图读取通道,看是否有值
select {
case t := <-timer.C:
fmt.Println("接收到定时器信号:", t.Format(time.DateOnly))
default:
fmt.Println("无信号")
}
quit <- true
}()
// 确保定时器已触发并发送信号
time.Sleep(3 * time.Second)
wasStopped := timer.Stop()
if wasStopped {
fmt.Println("定时器未过期,停止成功")
} else {
fmt.Println("定时器已经过期并且信号已经发送")
}
// 等待退出信号
<-quit
}
定时器已经过期并且信号已经发送
接收到定时器信号:2024-10-20
由于通道是有缓冲的,在定时器过期时已经发送了信号,因此即使在定时器触发之后调用 Stop()
方法,我们仍然可以从缓冲中接收到信号。
定时器未过期,停止成功
无信号
由于通道是无缓冲的,信号发送是一个阻塞操作。如果在信号被接收之前调用 Stop()
方法,这将阻止信号的发送。因此,定时器被成功停止, Stop()
返回 true
。
GODEBUG
的 asynctimerchan=1
,从而恢复到之前异步通道的行为。Go 1.23 版本中对 Timer 和 Ticker 进行了重要优化,包括两个主要方面:垃圾回收的改进和计时器通道行为的变化。改进后的垃圾回收机制有助于防止内存泄漏,而计时器通道的调整则确保在调用 Reset 或 Stop 之后,通道不会接收到任何旧数据,提高了定时器操作的可靠性和安全性。
如果您喜欢我的文章,请点击下面按钮随意打赏,您的支持是我最大的动力。
最新评论