func main() {
const timeout = 10 * time.Millisecond
// 10ms 超时
t := time.NewTimer(timeout)
// 当前 goroutine 先挂起个 20ms
time.Sleep(20 * time.Millisecond)
// 到这时 t 已经过期
start := time.Now()
// 重置 timer ,设置 10ms 的超时时间
t.Reset(timeout)
// 理想:在这里阻塞,10ms 后被唤醒
// t.C => make(chan Time, 1)
<-t.C
fmt.Printf("Time elapsed: %dms\n", time.Since(start).Milliseconds())
// 预期输出: Time elapsed: 10ms
// 实际输出: Time elapsed: 0ms
}
go 声称 1.23 解决了 reset 的问题?但我在 1.24 下测试发现还是老样子。翻遍了源码,按道理 t 没有第一时间执行<-t.C
应该不满足 needsAdd 的条件,就不会放到 p.timers.heap 最小堆上,channel 不应该有数据才对
![]() |
1
sagaxu 1 天前
1.23 开始用的是 unbuffered channel ,Reset()时取消了正阻塞中的写入吗?
|
3
Flourite OP 说错了,channel 没有改,还是有缓存的`make(chan Time, 1)`
|
5
Flourite OP @sagaxu 在 channel 的 len 跟 cap 判断函数改了,但这不是重点,我反复看 needsAdd 的判断,都不满足加入最小堆的条件,那么 t.C 是什么时候被发送数据的
|
![]() |
6
bv 19 小时 25 分钟前 ![]() 我试了,go1.23 1.24 中均输出 Time elapsed: 10ms ,这不正符合你的预期吗?
https://go.dev/play/p/D2wJ4DNYjcs |
![]() |
8
pike0002 18 小时 34 分钟前
是不是用 go.mod 了?把 go.mod 里面的版本改成 1.23 或之后
|
![]() |
9
lesismal 15 小时 14 分钟前
分两类:
1. 同步的:使用 t.C 的地方配合了 select ,其他的 context 甚至 default 之类的可以先于 t.C 解除阻塞并 t.Reset ,这种依赖高版本可以解决问题。如果担心以为高版本没问题但实际用的低版本导致问题,可以自己封装下 Reset 用 select <-t.C: default 避免这种情况 2. 异步的:很多场景是<-t.C 与 t.Reset 是不在同一个协程,而<-t.C 与 Reset 可能发生在同一个瞬间,Reset 并不能保证先于<-t.C 返回,这样就需要额外的一致性保障、除非这块的一致性要求不大 |