Golang中time.Timer的精度问题探讨
Golang中time.Timer的精度问题探讨
func TestTicker(wg sync.WaitGroup) {
calDuration := func(duration time.Duration) time.Duration {
now := time.Now()
return now.Truncate(duration).Add(duration).Sub(now)
}
go func(){
t := time.NewTimer(calDuration(time.Minute))
for {
<-t.C
fmt.Println(time.Now())
t.Reset(calDuration(time.Minute))
}
wg.Done()
}()
}
有时会出现一分钟内触发两次的情况,因为持续时间可能会缩短。这真的很奇怪。有人能帮我吗?谢谢
2018-07-19 14:36:00.003887996 +0800 CST m=+24.916092657
2018-07-19 14:37:00.002985076 +0800 CST m=+84.917119245
2018-07-19 14:38:00.001214551 +0800 CST m=+144.917278207
2018-07-19 14:39:00.000418561 +0800 CST m=+204.918411736
2018-07-19 14:39:59.999490194 +0800 CST m=+264.919412884
2018-07-19 14:40:00.000167519 +0800 CST m=+264.920090231
2018-07-19 14:40:59.99914446 +0800 CST m=+324.920996684
2018-07-19 14:41:00.000247228 +0800 CST m=+324.922099488
更多关于Golang中time.Timer的精度问题探讨的实战教程也可以访问 https://www.itying.com/category-94-b0.html
请注意,time.Truncate 方法会向下取整,因此您可能不是将完整的一分钟添加到当前时间,而是添加稍少的时间。这样理解对吗?
更多关于Golang中time.Timer的精度问题探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
感谢回复!那么您说的"向下取整"是什么意思?我不太理解为什么它会少加一点。您能帮我解释一下吗 :)
最初我以为你问的是为什么没有精确休眠一分钟。是我的错。
假设现在是12:15:30.91。
调用Truncate后得到12:15:30.00。
现在增加1分钟:12:16:30.00。
再减去当前时间:00:00:59.09。
既然你醒来的时间只提前了不到1毫秒,我怀疑这是由于计算开销和计时器管理机制造成的。如果有影响的话,你应该是会晚醒才对。我也怀疑这与单调时钟有关,但有可能。这是在什么类型的硬件上运行的?
纯粹出于好奇,如果你使用通道传递的时间而不是time.Now(),会得到什么结果?
抱歉,我一直在错过你的问题。我最初的回答适用于这两行代码:
2018-07-19 14:39:59.999490194 +0800 CST m=+264.919412884
2018-07-19 14:40:00.000167519 +0800 CST m=+264.920090231
注意当你进行计算时,当前时间是14:39:59.99,截断操作返回14:39:59.00,你加上一分钟得到14:40:00,然后减去当前时间得到00:00:00.001,这就是你需要等待的时长,所以你在一毫秒后再次唤醒。你能在下一毫秒流逝前成功挤入Reset调用的这个事实真是令人惊讶!
问题出在calDuration函数计算下一个整分钟时可能返回负值,导致计时器立即触发。当系统时间接近整分钟边界时,Truncate和Sub的组合可能产生负持续时间,因为time.Now()在计算过程中略有延迟。
以下是修正后的代码:
func TestTicker(wg *sync.WaitGroup) {
calDuration := func(duration time.Duration) time.Duration {
now := time.Now()
next := now.Truncate(duration).Add(duration)
return next.Sub(now)
}
go func(){
defer wg.Done()
t := time.NewTimer(calDuration(time.Minute))
defer t.Stop()
for {
select {
case <-t.C:
fmt.Println(time.Now())
t.Reset(calDuration(time.Minute))
}
}
}()
}
关键修改:
- 确保
calDuration总是返回正值 - 使用指针类型的
sync.WaitGroup - 添加
defer t.Stop()确保资源释放 - 使用select语句提供更好的控制
如果仍然遇到精度问题,可以考虑使用time.Ticker作为替代方案:
func TestTickerFixed(wg *sync.WaitGroup) {
go func(){
defer wg.Done()
// 先等待到下一个整分钟
now := time.Now()
firstDelay := now.Truncate(time.Minute).Add(time.Minute).Sub(now)
time.Sleep(firstDelay)
ticker := time.NewTicker(time.Minute)
defer ticker.Stop()
for {
select {
case <-ticker.C:
fmt.Println(time.Now())
}
}
}()
}
time.Ticker内部已经处理了时间漂移问题,通常能提供更稳定的定时触发。

