go timer踩坑记录,为什么,如何正确使用

业务需求:开一个循环处理外部发来的任务,如果一定时间内没有任务发过来就退出,处理完一个任务后刷新超时时间
然后我就写了这个

func handle(reqCh <-chan int) {
	a := time.NewTimer(time.Second)
	defer a.Stop()
	for {
		select {
		case <-a.C:
			fmt.Println("timeout", time.Now())
			return
		case req := <-reqCh:
			fmt.Println(req) // 业务 可能耗时较多
			fmt.Println(time.Now())
			a.Reset(time.Second)
		}
	}
}

然后发现明明req一直在发没有超时,但handle还是退出了

迷糊了一下,然后就加上了打印时间测试一下,发现退出时间基本等于req处理完后的时间,所以问题找到了:Reset没有"生效"

然后第一个想的办法是直接把Reset改成a = time.NewTimer(time.Second),问题是没有了,就是有点憨憨。

我认为go有一个比较好的地方,标准库的文档很详细,然后我就点开了Reset的文档。oh 获得了新知识....

  1. Reset有返回值. true和false含义不一样(这这里其实没有实际用处)
  2. NewTimer创建的Timer只能在Stop或者<-t.C以后调用Reset (我就是犯了这错)

然后官方给的建议是这个

if !t.Stop() {
    <-t.C
}
t.Reset(d)

并且贴心地给了一句 This should not be done concurrent to other receives from the Timer's channel.

原因是如果t被stop两次<-t.C就block住了。好的,一般来说正常人不可能去stop两次,也不太会去并发stop,所以其实问题不大

不过最大的问题是太丑了... 居然有4行。也行可以封装成一个函数比如ReliableResetTimer(*time.Timer,time.Duration)

最后总结一下

  1. time.Newtimer比较重,所以能Reset就Reset
  2. Reset之前必须Stop(或者执行<-t.C)

最后最后附上几个测试例子

func TestResetTimer(t *testing.T) {
	fmt.Println(time.Now())
	a := time.NewTimer(time.Millisecond * 500)
	if !a.Stop() {
		// 这里不会执行 Stop也有返回值  不多赘述
		fmt.Println(111, time.Now())
		fmt.Println(<-a.C)
	}
	a.Reset(time.Second)

	<-a.C
	fmt.Println(time.Now())
}

func TestResetTimer2(t *testing.T) {
	a := time.NewTimer(time.Millisecond * 500)
	a.Stop()
	if !a.Stop() {
		fmt.Println(111, time.Now())
		fmt.Println(<-a.C) // fatal error: all goroutines are asleep - deadlock!
	}
}

func BenchmarkResetAndNewTimer(b *testing.B) {
	for i := 0; i < b.N; i++ {
		a := time.NewTimer(time.Second)
		select { // 加上这个是因为不能直接给a赋值
		case <-a.C:
		default:
		}
		a.Stop()
		a.Reset(time.Second * 2) // 200ns/op
		//a = time.NewTimer(time.Second * 2) // 900ns/op
	}
}
posted @   xiaotushaoxia  阅读(80)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 如何使用 Uni-app 实现视频聊天(源码,支持安卓、iOS)
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
点击右上角即可分享
微信分享提示