Explicación detallada de Go Timer y el uso correcto de Reset y Stop

tiempo.temporizador

Timer es un temporizador de una sola vez en el timepaquete . Su función es activar eventos regularmente. Después de que se active, el temporizador dejará de ser válido y debe llamarse Reset()para que el temporizador vuelva a tener efecto.

type Timer struct {
	C <-chan Time
	// contains filtered or unexported fields
}
复制代码

Esta es una estructura de tipo Temporizador. Solo hay un canal para acceso externo. La función de este canal es enviar la hora actual a este canal después de que finaliza el cronometraje, de modo que cuando el canal recibe el valor, es igual al temporizador. Tiempo de espera, se pueden ejecutar eventos temporizados. Por lo tanto, generalmente se usa junto con la selectdeclaración .

El principio subyacente de Timer

En un programa, todos los temporizadores son mantenidos por una gorutina que ejecuta una timerproc()función . Utiliza el algoritmo del montón de tiempo para mantener todos los temporizadores. La estructura de datos subyacente es un pequeño montón raíz basado en una matriz. El elemento en la parte superior del montón es el temporizador más cercano al tiempo de espera. Esta gorutina se activará regularmente y leerá el Temporizador en la parte superior del montón. , ejecute la ffunción o envíe el tiempo y luego elimínelo de la parte superior del montón.

time.NewTimer() crea un temporizador

func NewTimer(d Duration) *Timer
复制代码

time.NewTimer()Es una de las formas de crear un temporizador. Al pasar un tiempo de temporización d, time.NewTimer()se devolverá el puntero del temporizador creado. Después dde tanto tiempo, el temporizador enviará la hora actual al canal en el temporizador.

timer := time.NewTimer(5 * time.Minute)
select {
    case <-timer.C:
       fmt.Println("timed out")
    default:
}
复制代码

Una vez finalizado el tiempo del Timer, selectse recibirá el valor enviado en el canal, para que se pueda ejecutar el evento cronometrado.

Detener () Detener temporizador

func (t *Timer) Stop() bool
复制代码

Stop() 是 Timer 的成员函数,调用 Stop() 方法,会中止这个 Timer 的计时,使其失效,之后是不会触发定时事件的。

调用 Stop() 方法之后,会将这个 Timer 从时间堆里移除,如果这个 Timer 还没超时,依然在时间堆中,那么就会被成功移除并且返回 true;如果这个 Timer 不在时间堆里,说明已经超时了或者已经被 stop 了,这个时候就会返回 false

Reset() 重置 Timer

func (t *Timer) Reset(d Duration) bool
复制代码

Reset() 是 Timer 里的另一个成员函数,它的作用是重置这个 Timer。如果这个 Timer 已经超时失效了,那么 Reset() 会令其重新生效;如果这个 Timer 还没超时,那么 Reset() 会让其重新计时,并将超时时间设置为 d

这里有一个需要注意的地方,在官方的 package 文档中,有这么一句话:

For a Timer created with NewTimer, Reset should be invoked only on stopped or expired timers with drained channels.

意思是调用 Reset() 之前,一定要保证这个 Timer 已经被 stop 了,或者这个 Timer 已经超时了,并且里面 channel 已经被排空了。

因为,如果这个 Timer 还没超时,但是不去保证这个 Timer 已经被 stop 了,那么旧的 Timer 依然存在时间堆里,并且依然会触发,就会产生意料之外的事。而如果这个 Timer 已经超时了,不在时间堆里了,但是可能是刚刚超时,并且往 channel 里发送了时间,如果不显式排空 channel 的话,那么也会触发超时事件,所以需要显式地排空 channel。

所以正常情况下,Reset() 要和 Stop() 一起搭配使用。官方文档里给出了示例:

if !t.Stop() {
	<-t.C
}
t.Reset(d)
复制代码

这样可以同时保证这个 Timer 已经被 stop 了,或者这个 Timer 已经超时了,但是对 channel 进行了显式排空。

但是这里存在一个问题,在正常情况下,如果之前的 Timer 还生效,那么 Stop() 会返回 true,不会产生问题;但是如果 Timer 已经超时了,Stop() 就会返回 false,而如果 channel 里面没有没有值,那么就会发生阻塞,导致程序卡在这里。

所以更好的做法是采用 select

if !t.Stop() {
    select {
    case <-t.C: // try to drain the channel
    default:
    }
}
t.Reset(d)
复制代码

这样即使 channel 里面没有值,也不会发生阻塞,有值的话也可以成功排空 channel。

但是,显式排空 channel 并不是绝对的,如果 channel 里面存在值,但是对你想要的结果不会产生任何影响的话,那么不显式排空 channel 也是可以的,直接在 Reset() 之前调用一次 Stop() 就行,也不需要对 Stop() 的返回值进行判断。

time.AfterFunc() 创建 Timer

对于 time.Timer,还有另一种创建方式:time.AfterFunc()。(time 包里还有其他几种计时器,这篇文章只讨论 time.Timer 这种计时器)

func AfterFunc(d Duration, f func()) *Timer
复制代码

time.AfterFunc()time.NewTimer() 相比,参数上多了一个 f。这是因为 time.AfterFunc() 创建的 Timer 在超时之后会在一个新的 goroutine 中执行这个 f 函数,不会向 channel 里面发送值。

之前所讨论的需要 Stop() 之后显式排空 channel 的情况都是对于 time.NewTimer() 创建的 Timer 来说,对于 time.AfterFunc() 来说,由于不会向 channel 里发送值,所以不需要显式排空 channel 的额外操作,但是在 Reset() 之前还是需要调用 Stop() 的。

此外,Stop()Reset() 的返回值对于 time.AfterFunc() 创建的 Timer 来说含义与之前提到的是不一样的。需要自己视情况而定,看 f 函数的执行与否对结果来说有没有影响,如果有影响,那么就需要额外判断返回值,如果没有影响,直接调用即可。

参考资料

[1] 论golang Timer Reset方法使用的正确姿势

[2] package time

[3] How Do They Do It: Timers in Go

[4] Golang Timer 源码探索及常见问题

Supongo que te gusta

Origin juejin.im/post/7079255932152053773
Recomendado
Clasificación