Closed yangwenmai closed 5 years ago
第一个链接有部分内容的理解是错误的
其次golang的处理方式中也可以看出,
go的timer的处理和用户端程序定义的间隔时间不一定完全精准,
用户的回调函数执行时间越长单个timer对堆中其他邻近timer的影响越大。
因此timer的回调函数一定是执行时间越短越好。
事实上标准库的封装考虑到了这个问题,所以内置的回调函数只有这两个
// ...
func sendTime(c interface{}, seq uintptr) {
// Non-blocking send of time on c.
// Used in NewTimer, it cannot block anyway (buffer).
// Used in NewTicker, dropping sends on the floor is
// the desired behavior when the reader gets behind,
// because the sends are periodic.
select {
case c.(chan Time) <- Now():
default:
}
}
// ...
func goFunc(arg interface{}, seq uintptr) {
go arg.(func())()
}
换句话说,都是非阻塞的,而用户的自定义回调函数,是作为arg传进去然后分配新的g去执行的,所以不存在下面说的这个问题
//...
用户的回调函数执行时间越长单个timer对堆中其他邻近timer的影响越大。
因此timer的回调函数一定是执行时间越短越好。
第3个文章是我写的,有问题请指出
@Shitaibin 第三篇文章写的挺好的,就是篇幅有点长了,而且Stop
和Reset
的api文档已经写了,所以应该不算陷阱把。。然后我觉得这种写法会更好
if !t.Stop() {
select {
case <-t.C:
default:
}
}
主要区别就是这里会对t.C加锁,但len(t.C)不会对t.C加锁.
不过严格来说的话,这些逻辑都在timer.Lock
外,所以没有执行顺序保证,那么完全有可能存在这样一条path,
1.timeproc发现timer过期,删除timer,然后解锁,准备执行sendtime
2.用户执行t.Stop,对timerbucket加锁,发现timer已经被删除,所以返回false
3.用户消费t.C失败,因为timerproc还没有执行sendtime
4.timerproc执行sendtime成功
这种概率很低,但是确实是个问题,而且我也没想到更好的写法。
如果想要复用 timer 重复设置超时,可以参考 http://github.com/google/netstack/tcpip/transport/tcp/timer.go 重复使用时保存新的 target.
@MaruHyl 我没有明白你说的这种方式更好在哪,Timer.Stop()
会停止继续向通道中写数据,就算超时也不会再写数据,也就是没有goroutine继续向Timer.C
写数据了,这时调用者len(Timer.C)
,本质上只有1个goroutine访问Timer.C的数据了,我认为和Timer.C是否加锁无关。
另外,timer.Lock
指啥,Timer和runtimeTimer都没看到这个Lock字段。
@Astone-Chou 这个思路很6,有一个地方没看懂,请教下:
enable函数是开始复用1个timer:
// enable enables the timer, programming the runtime timer if necessary.
func (t *timer) enable(d time.Duration) {
t.target = time.Now().Add(d)
// Check if we need to set the runtime timer.
if t.state == timerStateDisabled || t.target.Before(t.runtimeTarget) {
t.runtimeTarget = t.target
t.timer.Reset(d)
}
t.state = timerStateEnabled
}
调用的地方有这种情况:没有检查timer是否disable()就直接调用enable:
那是否就需要在调用者的代码逻辑上,确保在enable()时,之前设置的timer在已经不用了?
重复 enable 实际上就是你上面说的 reset 一个已经启动的定时器。同时修改 target。再 resume 的时候检测一下是不是最新 enable 的定时器(如果是老的定时器触发的就重新Reset)
@Shitaibin 会的,可能会存在两个goroutine在访问timer.C,timeproc和消费段。 timer.Lock指的是https://github.com/golang/go/blob/master/src/runtime/time.go#L64。
@Astone-Chou 你发的那个适用场景太特殊了,有点偏离我指的复用timer的主题(比如timer pool,完全独立的goroutines复用timer,上一次putback的timer不会影响下一次复用)。
然而你给的tcp-timer
并不是,它为了减少runtime call
的次数,disable
只是重置了timerState
,甚至由于timerStateOrphaned
状态的存在,重新enable
的时候可能导致timer过早的唤醒(如果timerStateOrphaned
且newTarget > oldTarget
,timer
不会被Reset
,同样也是为了减少runtime call
),但是这些trick都是针对于它的场景做的优化。
如果是一个timer pool的场景,你putback的时候就必须要真正的Stop掉timer,并且考虑如何保证清空掉timer.C才不会影响到下一次的使用。
@MaruHyl 你是对的,存在你说的这种路径,这就导致了最后t.C里还有个数据
参考资料