gitee.com/quant1x/gox@v1.21.2/coroutine/rolling_once.go (about) 1 package coroutine 2 3 import ( 4 "gitee.com/quant1x/gox/logger" 5 "gitee.com/quant1x/gox/runtime" 6 "gitee.com/quant1x/gox/timestamp" 7 "sync" 8 "sync/atomic" 9 "time" 10 ) 11 12 const ( 13 // 滑动窗口的锚点毫秒数, 默认0 14 rollingAnchorPoint = 0 15 // 窗口相对于锚点的偏移量 16 rollingWindow = timestamp.MillisecondsPerDay 17 // 相对于默认, 每天9点整 18 offsetWindow = timestamp.MillisecondsPerHour * 9 19 ) 20 21 // 计算下一个时间窗口 22 // 当前时间戳和当前观察点+偏移量比较 23 // 下一个时间窗口observer+滑动窗口尺寸 24 func nextTimeWindow(observer, rollingWindow int64) (next, current int64, canSwitch bool) { 25 now := timestamp.Now() 26 next = observer + rollingWindow 27 if now >= next { 28 canSwitch = true 29 } 30 elapsed := timestamp.SinceZero(now) 31 current = elapsed 32 return 33 } 34 35 // 获取当前观察点 36 // 当日0的毫秒数zero + offsetMilliSeconds 37 func getCurrentObserver(offsetMilliSeconds int64) int64 { 38 zero := timestamp.Today() 39 return zero + offsetMilliSeconds 40 } 41 42 // RollingOnce 周期性懒加载机制 43 type RollingOnce struct { 44 done uint32 45 m sync.Mutex 46 once sync.Once // for ticker 47 ticker *time.Ticker // 定时器 48 currentWindow int64 // 滑动窗口的毫秒数, 这里默认1天 49 currentOffset atomic.Int64 // 相对于0点整的偏移毫秒数 50 currentObserver atomic.Int64 // 当前窗口期起点的毫秒数 51 lazyFunc func() // 懒加载函数指针 52 finished chan struct{} // 关闭ticker的信号 53 } 54 55 // Close 资源关闭方法 56 func (o *RollingOnce) Close() { 57 // 发送结束信号 58 o.finished <- struct{}{} 59 close(o.finished) 60 } 61 62 // GetCurrentAnchorPoint 获取当前时间窗口期的锚点 63 func (o *RollingOnce) GetCurrentAnchorPoint() int64 { 64 return o.currentObserver.Load() 65 } 66 67 func (o *RollingOnce) initTicker() { 68 // 1. 设置窗口期 69 o.currentWindow = rollingWindow 70 // 2. 第一步初始化offset, // 偏移默认是常量offsetWindows 71 o.currentOffset.CompareAndSwap(0, offsetWindow) 72 // 3. 第二步初始化当前时间窗口观察点 73 o.currentObserver.CompareAndSwap(0, getCurrentObserver(o.currentOffset.Load())) 74 o.finished = make(chan struct{}) 75 if o.ticker == nil { 76 go o.runTicker() 77 } 78 } 79 80 // SetOffsetTime 用小时数,分钟数设置滑动窗口的偏移量 81 func (o *RollingOnce) SetOffsetTime(hour, minute int) { 82 offset := timestamp.MillisecondsPerHour * hour 83 offset += timestamp.MillisecondsPerMinute * minute 84 o.SetOffsetForZero(int64(offset)) 85 } 86 87 // SetOffsetForZero 设置时间窗口变化的偏移量 88 // 89 // 为非默认9点整重置done预留的功能性方法 90 func (o *RollingOnce) SetOffsetForZero(offsetMilliSeconds int64) { 91 o.currentOffset.Store(offsetMilliSeconds) 92 o.updateObserverOfWindow() 93 } 94 95 // 更新窗口期的观察点 96 func (o *RollingOnce) updateObserverOfWindow() { 97 o.currentObserver.Store(getCurrentObserver(o.currentOffset.Load())) 98 } 99 100 // WindowIsExpired 检查当前窗口期的是否过期 101 func (o *RollingOnce) WindowIsExpired() bool { 102 _, _, canSwitch := nextTimeWindow(o.currentObserver.Load(), o.currentWindow) 103 return canSwitch 104 } 105 106 func (o *RollingOnce) runTicker() { 107 funcName := runtime.FuncName(o.lazyFunc) 108 o.ticker = time.NewTicker(100 * time.Millisecond) 109 defer o.ticker.Stop() 110 for { 111 select { 112 case <-o.ticker.C: 113 // 检查滑动窗口期是否过时 114 if o.WindowIsExpired() { 115 if runtime.Debug() { 116 logger.Infof("RollingOnce[%s]: reset begin", funcName) 117 } 118 o.Reset() 119 if runtime.Debug() { 120 logger.Infof("RollingOnce[%s]: reset end", funcName) 121 } 122 } 123 case <-o.finished: 124 // 收到结束信号, 退出循环 125 break 126 } 127 } 128 } 129 130 func (o *RollingOnce) Do(f func()) { 131 if o.lazyFunc == nil { 132 o.lazyFunc = f 133 } 134 o.once.Do(o.initTicker) 135 if atomic.LoadUint32(&o.done) == 0 { 136 o.doSlow(f) 137 } 138 } 139 140 func (o *RollingOnce) doSlow(f func()) { 141 o.m.Lock() 142 defer o.m.Unlock() 143 if o.done == 0 { 144 defer atomic.StoreUint32(&o.done, 1) 145 f() 146 } 147 } 148 149 // Reset 被动的方式重置初始化done标志 150 func (o *RollingOnce) Reset() { 151 if atomic.LoadUint32(&o.done) == 1 { 152 o.resetSlow() 153 } 154 } 155 156 func (o *RollingOnce) resetSlow() { 157 o.m.Lock() 158 defer o.m.Unlock() 159 if o.done == 1 { 160 atomic.StoreUint32(&o.done, 0) 161 // 重置观察点 162 o.updateObserverOfWindow() 163 } 164 }