Go语言无需额外通道优雅停止Timer是否可行?
如何仅用time.Timer.C实现无泄漏的定时器启停处理
问题场景
现有代码通过启动协程监听time.Timer.C处理定时器超时,但存在两个核心问题:
- 若在定时器超时前调用
Stop(),协程会因阻塞在<-timer.C无法退出,造成协程泄漏; - 若定时器已超时,
Stop()返回false,此时主goroutine尝试读取timer.C会触发死锁(因为协程已经取走了通道内的值)。
要求不使用额外通道,仅借助time.Timer.C实现安全的定时器启停与超时响应。
原测试代码
package main import ( "runtime" "testing" "time" ) func testTimerRoutine(expire bool) bool { dur := 10 * time.Millisecond timer := time.NewTimer(dur) leak := true go func() { defer func() { println("timer routine quit") leak = false }() <-timer.C println("timer expired") }() if expire { time.Sleep(2 * dur) } if !timer.Stop() { println("timer stop false") <-timer.C } else { println("timer stop true") } runtime.GC() time.Sleep(10 * dur) return leak } func TestTimerRoutineDeadlock(t *testing.T) { leak := testTimerRoutine(true) if leak { t.Errorf("routine leak") } } func TestTimerRoutineLeak(t *testing.T) { leak := testTimerRoutine(false) if leak { t.Errorf("routine leak") } }
解决方案
核心思路是利用Timer.Reset()方法在成功停止定时器后,主动触发timer.C让协程正常退出,同时避免主goroutine在定时器超时后重复读取通道。修改后的代码如下:
package main import ( "runtime" "testing" "time" ) func testTimerRoutine(expire bool) bool { dur := 10 * time.Millisecond timer := time.NewTimer(dur) leak := true go func() { defer func() { println("timer routine quit") leak = false }() <-timer.C println("timer expired") }() if expire { time.Sleep(2 * dur) } stopped := timer.Stop() if stopped { println("timer stop true") // 重置定时器为0,立即向timer.C发送值,让协程退出 timer.Reset(0) } else { println("timer stop false") // 定时器已超时,协程已读取timer.C,无需再操作 } runtime.GC() time.Sleep(10 * dur) return leak } func TestTimerRoutineDeadlock(t *testing.T) { leak := testTimerRoutine(true) if leak { t.Errorf("routine leak") } } func TestTimerRoutineLeak(t *testing.T) { leak := testTimerRoutine(false) if leak { t.Errorf("routine leak") } }
方案原理
- 解决协程泄漏:当
timer.Stop()返回true时,说明定时器尚未触发,协程正阻塞在<-timer.C。调用timer.Reset(0)会立即触发定时器,向timer.C发送一个值,协程读取后即可执行defer逻辑正常退出,避免泄漏。 - 避免死锁:当
timer.Stop()返回false时,说明定时器已经触发,timer.C内的值已被协程读取,主goroutine无需再尝试读取通道,自然不会发生死锁。
测试验证
修改后的代码可通过两个测试用例:
TestTimerRoutineDeadlock:定时器超时后,协程正常退出,无泄漏;TestTimerRoutineLeak:提前停止定时器后,Reset(0)触发通道,协程正常退出,无泄漏。
内容的提问来源于stack exchange,提问作者progquester
相关产品推荐
相关产品推荐

