20210120再战timerticker(1)关于析构的正确逻辑思路 - ziyouzy/2021blog GitHub Wiki

首先先把标准使用方式摆出来:

timer: timer := time.NewTimer(3 * time.Second) for { timer.Reset(3 * time.Second) // 这里复用了 timer select { case <-timer.C: fmt.Println("每隔3秒执行一次") } }

ticker: ticker := time.NewTicker(3 * time.Second)   for range ticker.C {    fmt.Print("每隔3秒执行任务")   } ticker.Stop()

/*******/

之后是如何正确的他们:

timer: timer.Stop()会返回true或false true:关闭前计时器未超时 false:关闭前计时器已超时

对于timer来说:

未超时的状态是很常见的,尤其是当整个模块需要进行析构操作的时候,比如用timer进行套接字连接心跳包的连接断开操作,往往到期时间会大于10秒甚至30秒,一旦出现类似软件退出的操作,必须要先去主动stop()才行
但是经过如下测试发现了一个新现象,那就是如果当前timer关闭前未超时则len(timer.C)=0
已超时则len(timer.C)>0
总之为安全起见,如下操作在析构时是必须的: if len(timer1.C)>0 { <-timer1.C }
同时通过如下代码验证,timer的内部字段管道timer.C是无法用内置函数close直接关闭的(编译根本就无法通过) timer1 := time.NewTimer(time.Second * 5) close(timer1.C) #invalid operation: close(timer1.C) (cannot close receive-only channel)

timer1 := time.NewTimer(time.Second * 5) 
time.Sleep(time.Second * 8)
close(timer1.C)	
#invalid operation: close(timer1.C) (cannot close receive-only channel)  

因此得出如下结论: 1.timer只需考虑其自身的生命周期问题 2.timer.C确实需要进行析构操作,但是只能做到 if len(timer1.C)>0 { <-timer1.C }这一步,无法对他进行真正的销毁(close) 3.timer.C的生命周期不会附属于timer,恰恰相反,timer的生命周期其实是timer.C决定的

对于ticker来说:

ticker的功能性方法只有Stop()这一个,不像timer那样同时有Reset()bool和Stop()bool两个
但是Stop的特性和timer是不一样,ticker的Stop()没有返回值
因此也就是说,ticker的使用方式是比较简单的
并不需要判断关闭时他是否未超时,或者说,他永远是未超时的状态,因为一个时间周期结束后就会立刻进入下个时间周期了
不过在析构时管道的排空依然是必须的,都是为了安全着想
其实不然,对于timer来说,需要确保到期后的管道内数据必须被主逻辑读取
主逻辑读取的本质其实是出发case分支,进行分支下的操作,不能在这些操作还没执行前就将整体析构,否则会造成逻辑丢失的,因此应该是这样的顺序: 1.timer.Stop判断是到期前还是到期后: a.未到期则执行if len(timer1.C)>0 { <-timer1.C },丢弃“那一瞬间”的数据,这才符合逻辑 b.已到期则执行if len(timer1.C)>0 { time.Sleep(time.Second) },让主逻辑完成未完成的case,这才符合逻辑 因为所谓的符合逻辑,核心在于Stop的那一瞬间之前未完成操作都必须有个结果,而之后即使有结果也不能留 2.ticker.Stop没有判断是否到期的逻辑,因为他永远都是未到期的状态: only.未到期则执行if len(timer1.C)>0 { <-timer1.C },丢弃“那一瞬间”的数据,这才符合逻辑

同样无法进行close(ticker1.C)的操作,同样编译不过去

ticker1 :=time.NewTicker(time.Second * 5)
close(ticker1.C)
#invalid operation: close(ticker1.C) (cannot close receive-only channel)