Go 定时器使用技巧:避免常见陷阱
Go 的定时器(time.Timer 和 time.Ticker)是并发编程的常用工具,但使用不当会导致资源泄漏甚至程序崩溃。以下是几个关键技巧。
1. 确保 Timer 资源释放
time.After 函数返回一个 channel,但背后的 Timer 在到期前不会被垃圾回收。如果在 select 中频繁使用 time.After(如循环内),会导致大量未过期的 Timer 残留。
错误示例:
for {
select {
case <-ch:
// 处理
case <-time.After(5 * time.Second):
// 超时
}
}
改进: 使用 time.NewTimer 并手动停止。
timer := time.NewTimer(5 * time.Second)
for {
select {
case <-ch:
// 处理
case <-timer.C:
// 超时
}
if !timer.Stop() {
<-timer.C
}
timer.Reset(5 * time.Second)
}
2. Ticker 必须显式停止
time.Ticker 如果不停止,会持续消耗系统资源。使用 defer ticker.Stop() 确保释放。
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for range ticker.C {
// 周期性任务
}
3. 正确处理 Timer 的 Stop 返回值
timer.Stop() 返回 false 表示 Timer 已经触发,此时需要清空 channel,否则后续 Reset 可能导致 channel 阻塞。
if !timer.Stop() {
select {
case <-timer.C:
default:
}
}
timer.Reset(duration)
4. 使用 time.AfterFunc 避免阻塞
time.AfterFunc 在指定时间后启动一个 goroutine 执行函数,不阻塞当前协程。适合延迟任务。
time.AfterFunc(10*time.Second, func() {
fmt.Println("10秒后执行")
})
掌握这些技巧,能让你写出更健壮的 Go 定时器代码,避免资源泄漏和意外行为。