client-go和golang源码中的技巧( 三 )

  • 定时器ticker定时器
首先看一下一般使用的定时器 , client-go中比较复杂的定时器也是在此基础上封装的 。 下面例子中给出的是ticker定时器 , 它会按照一定的时间频率往Ticker.C中发time.Time类型的数据 , 可以在协程中通过判断Ticker.C来执行定时任务 。 下例来自官方 , 实现每秒执行一次打印 ,
import ("fmt""time")func main(){ticker := time.NewTicker(time.Second)defer ticker.Stop()done := make(chan bool)go func() {time.Sleep(10 * time.Second)done <- true}()for {select {case <-done:fmt.Println("Done!")returncase t := <-ticker.C:fmt.Println("Current time: ", t)}}}结果:Current time:2019-07-04 14:30:37.9088968 +0800 CST m=+5.328291301Current time:2019-07-04 14:30:38.9089349 +0800 CST m=+6.328328801Current time:2019-07-04 14:30:39.9101415 +0800 CST m=+7.329534901Current time:2019-07-04 14:30:40.9095174 +0800 CST m=+8.328910201Current time:2019-07-04 14:30:41.9092961 +0800 CST m=+9.328688301Current time:2019-07-04 14:30:42.9087682 +0800 CST m=+10.328159801Current time:2019-07-04 14:30:43.9088604 +0800 CST m=+11.328251401Current time:2019-07-04 14:30:44.909609 +0800 CST m=+12.328999501Current time:2019-07-04 14:30:45.9094782 +0800 CST m=+13.328868101Current time:2019-07-04 14:30:46.909006 +0800 CST m=+14.328395401Done!需要注意的是使用ticker并不能保证程序被精确性调度 , 如果程序的执行时间大于ticker的调度周期 , 那么程序的触发周期会发生偏差(可能由于系统cpu占用过高 , 网络延迟等原因) 。 如下面例子中 , ticker触发周期为1s , 但程序执行大于2s , 此时会出现程序执行频率不一致的情况 。 适用于周期性触发一个任务 。
func main(){ticker := time.NewTicker(time.Second)defer ticker.Stop()done := make(chan bool)go func() {time.Sleep(10 * time.Second)done <- true}()for {select {case <-done:fmt.Println("Done!")returncase t := <-ticker.C:time.Sleep(time.Second*2)fmt.Println("Current time: ", t)}}}结果:Current time:2019-07-04 14:56:52.5446526 +0800 CST m=+5.281916601Current time:2019-07-04 14:56:53.5452488 +0800 CST m=+6.282512201//和上一条相差1s , 但和下一条相差2sCurrent time:2019-07-04 14:56:55.5443528 +0800 CST m=+8.281615101Current time:2019-07-04 14:56:57.5449183 +0800 CST m=+10.282179401Current time:2019-07-04 14:56:59.5448671 +0800 CST m=+12.282127101Done!
  • timer定时器
timer的机制和ticker相同 , 在定时器超时后往一个chan中发送time.Time数据 。 不同的是ticker可以周期性调度 , timer只会执行一次 , 如果需要重复调度 , 需要使用Reset函数重置timer 。 利用该机制 , 可以在同一个timer上以不同间隔调度程序 。
func main(){timer := time.NewTimer(time.Second)defer timer.Stop()t := <-timer.Cfmt.Println("Current time: ", t)timer.Reset(time.Second*2)t = <-timer.Cfmt.Println("Current time: ", t)timer.Reset(time.Second*3)t = <-timer.Cfmt.Println("Current time: ", t)}结果:Current time:2019-07-04 15:47:01.7518201 +0800 CST m=+5.312710501Current time:2019-07-04 15:47:03.7766692 +0800 CST m=+7.337558501Current time:2019-07-04 15:47:06.7770913 +0800 CST m=+10.337978901使用timer需要注意Reset函数只能在timer超时后使用 , 否则将无效 。 因为Timer.C的长度只有1 , 如果前面一个定时器结束前执行了Reset , 那么前面的定时器会被取消 。 具体可以参见这里
func NewTimer(d Duration) *Timer {c := make(chan Time, 1)...}下面例子中可以看出 , 多次执行Reset并不会多次触发定时任务 , 在前一个定时器超时前执行Reset , 会取消前一个定时器并以Reset中的duration开始计时 。
func main(){fmt.Println("now time: "time.Now())timer := time.NewTimer(time.Second*5)defer timer.Stop()timer.Reset(time.Second*2)timer.Reset(time.Second*2)timer.Reset(time.Second*2)go func() {for ; ;{select {case t:=<- timer.C:fmt.Println("Current time: ", t)}}}()time.Sleep(time.Second*10)}结果:now time:2019-07-04 16:16:31.7246084 +0800 CST m=+4.281414201Current time:2019-07-04 16:16:33.7505395 +0800 CST m=+6.307344201官方推荐的用法如下 , 由于没有加锁 , 此方法不能在多个协程中同时使用 。
if !t.Stop() {<-t.C}t.Reset(d)func AfterFunc(d Duration, f func()) *Timer函数用于在d时间超时后 , 执行f函数 。 注意返回的timer需要手动stop
timer := time.AfterFunc(time.Second*5, func() {fmt.Println("timeout")})time.Sleep(time.Second*6)timer.Stop()更多timer的用法可以参见官方文档
  • wait实现(k8s.io/apimachinery/pkg/util/wait/wait.go)
    • wait中实现了很多与定时相关的函数 , 首先来看第一组:
func Forever(f func(), period time.Duration) func Until(f func(), period time.Duration, stopCh <-chan struct{}) func UntilWithContext(ctx context.Context, f func(context.Context), period time.Duration) func NonSlidingUntil(f func(), period time.Duration, stopCh