圆月山庄资源网 Design By www.vgjia.com
定时器是什么
Golang 原生 time 包下可以用来执行一些定时任务或者是周期性的任务的一个工具
本文基于 Go 1.14,如果以下文章有哪里不对或者问题的地方,欢迎讨论学习
定时器的日常使用
Timer 相关
func NewTimer(d Duration) *Timer func (t *Timer) Reset(d Duration) bool func (t *Timer) Stop() bool func After(d Duration) <-chan Time func AfterFunc(d Duration, f func()) *Timer func main() { timer := time.NewTimer(3 * time.Second) select { case <-timer.C: fmt.Println("3秒执行任务") } timer.Stop() // 这里来提高 timer 的回收 } func main() { tChannel := time.After(3 * time.Second) // 其内部其实是生成了一个 timer select { case <-tChannel: fmt.Println("3秒执行任务") } } func main() { timer := time.NewTimer(3 * time.Second) for { timer.Reset(4 * time.Second) // 这样来复用 timer 和修改执行时间 select { case <-timer.C: fmt.Println("每隔4秒执行任务") } } }
注意事项:
错误使用:time.After 这里会不断生成 timer,虽然最终会回收,但是会造成无意义的cpu资源消耗
func main() { for { select { case <-time.After(3 * time.Second): fmt.Println("每隔3秒执行一次") } } }
正确使用:
func main() { timer := time.NewTimer(3 * time.Second) for { timer.Reset(3 * time.Second) // 这里复用了 timer select { case <-timer.C: fmt.Println("每隔3秒执行一次") } } }
Ticker 相关
func NewTicker(d Duration) *Ticker func Tick(d Duration) <-chan Time func (t *Ticker) Stop() func main() { ticker := time.NewTicker(3 * time.Second) for range ticker.C { fmt.Print("每隔3秒执行任务") } ticker.Stop() }
错误使用:
func main() { for { select { case <-time.Tick(3 * time.Second): // 这里会不断生成 ticker,而且 ticker 会进行重新调度,造成泄漏(后面源码会有解析) fmt.Println("每隔3秒执行一次") } } }
定时器源码分析
我先给出涉及到过程的相关结构体(!!!要注意 Timer 和 timer 的不同)
type Timer struct { C <-chan Time r runtimeTimer } "htmlcode">func NewTicker(d Duration) *Ticker { if d <= 0 { panic(errors.New("non-positive interval for NewTicker")) } c := make(chan Time, 1) t := &Ticker{ C: c, r: runtimeTimer{ when: when(d),//当前时间+d的时间,可看下面 period: int64(d),//执行周期 f: sendTime, arg: c, // 就是 f 中第一个参数 }, } startTimer(&t.r) return t } "htmlcode">// startTimer adds t to the timer heap. // 这里已经说明了 timers 是一种堆的数据结构,由于是定时器, // 最近的最先执行,所以猜测以 when 来判断的小顶堆 func startTimer(t *timer) { addtimer(t) } "addtimer called with initialized timer") } t.status = timerWaiting "cleantimers: bad p") } switch s := atomic.Load(&t.status); s { case timerDeleted: if !atomic.Cas(&t.status, s, timerRemoving) {// status 变更为 timerRemoving continue } dodeltimer0(pp) // 这里是删除 timer 的关键部分,删除堆顶的部分并调整 if !atomic.Cas(&t.status, timerRemoving, timerRemoved) { // stauts 变更为 timerRemoved badTimer() // 这里就是 throw 一个异常 } atomic.Xadd(&pp.deletedTimers, -1) case timerModifiedEarlier, timerModifiedLater: if !atomic.Cas(&t.status, s, timerMoving) { // stauts 变更为 timerMoving continue } t.when = t.nextwhen // 将执行时间设置为其下次执行的时候 // -----删除堆顶位置,并按照其新的执行时间加入到对应的位置 dodeltimer0(pp) doaddtimer(pp, t) // 添加 timer 的关键部分 // ------------ if s == timerModifiedEarlier { atomic.Xadd(&pp.adjustTimers, -1) } if !atomic.Cas(&t.status, timerMoving, timerWaiting) { badTimer() } default: return } } } "dodeltimer0: wrong P") } else { t.pp = 0 // 这里将指针情况 } // --- 将堆的最后一位 timer 放到堆顶,然后清空最后一位的空间,然后向下调整--- last := len(pp.timers) - 1 if last > 0 { pp.timers[0] = pp.timers[last] } pp.timers[last] = nil pp.timers = pp.timers[:last] if last > 0 { siftdownTimer(pp.timers, 0)//向下调整的核心部分 } // --------------------- updateTimer0When(pp) //更新当前 p 的最先执行 timer 的执行时间 atomic.Xadd(&pp.numTimers, -1) } "doaddtimer: P already set in timer") } t.pp.set(pp) // --- 将 timer 放置到堆的最后一位,然后向上调整 --- i := len(pp.timers) pp.timers = append(pp.timers, t) siftupTimer(pp.timers, i)// 向上调整的核心部分 // --------------------------- if t == pp.timers[0] { atomic.Store64(&pp.timer0When, uint64(t.when)) } atomic.Xadd(&pp.numTimers, 1) }当我们已知 timers 是小顶堆的数据结构(满足“当前位置的值小于等于父位置的值“即可,实现方式使用数组,由下面代码可以知道是四叉小顶堆,结构如下图)的情况后,接下来看堆向上或者向下调整的细节部分
// timers 堆的向上调整 func siftupTimer(t []*timer, i int) { ... when := t[i].when tmp := t[i] for i > 0 { p := (i - 1) / 4 // 由这里可以看出,堆的节点长度是4 if when >= t[p].when { break } // --- 向上进行调整,即父节点下移,当前节点上移 --- t[i] = t[p] i = p //向上进行调整 } if tmp != t[i] { t[i] = tmp } } "htmlcode">// 这里执行的前提是当前 P 的 timesLock 已经锁了,所以不用担心并发问题 func runtimer(pp *p, now int64) int64 { for { t := pp.timers[0] //找到 timers 堆的堆顶,为最先执行的 timer if t.pp.ptr() != pp { throw("runtimer: bad p") } switch s := atomic.Load(&t.status); s { case timerWaiting: if t.when > now { //如果还没到时间,则返回调用的时间 return t.when } "htmlcode">// 由于是 runtimer 进行调用,因此也线程安全 func runOneTimer(pp *p, t *timer, now int64) { ... f := t.f arg := t.arg seq := t.seq "text-align: center">我们看下 checkTimers 做了什么
func checkTimers(pp *p, now int64) (rnow, pollUntil int64, ran bool) { if atomic.Load(&pp.adjustTimers) == 0 {// 如果没有需要可调整的,则直接返回最先执行 timer 的时间 next := int64(atomic.Load64(&pp.timer0When)) if next == 0 { return now, 0, false } if now == 0 { now = nanotime() } if now < next { // 表示还没有到执行时间 if pp != getg().m.p.ptr() || int(atomic.Load(&pp.deletedTimers)) <= int(atomic.Load(&pp.numTimers)/4) { //且要删除的 Timer数量小于 Timer总数的1/4 return now, next, false } } } "htmlcode">func adjusttimers(pp *p) { if len(pp.timers) == 0 { return } if atomic.Load(&pp.adjustTimers) == 0 { // 如果需要调整的 Timer 为 0,则直接返回 ... return } var moved []*timer loop: for i := 0; i < len(pp.timers); i++ { t := pp.timers[i] if t.pp.ptr() != pp { throw("adjusttimers: bad p") } switch s := atomic.Load(&t.status); s { case timerDeleted: // 这里就是将部分需要删除的 Timer 给清理掉 if atomic.Cas(&t.status, s, timerRemoving) { dodeltimer(pp, i) if !atomic.Cas(&t.status, timerRemoving, timerRemoved) { badTimer() } atomic.Xadd(&pp.deletedTimers, -1) i-- } case timerModifiedEarlier, timerModifiedLater: // 把需要调整 Timer 放到 moved 中,然后删除当前堆的数据进行堆调整,后续将 moved 通过 addAdjustedTimers 添加 if atomic.Cas(&t.status, s, timerMoving) { t.when = t.nextwhen dodeltimer(pp, i) moved = append(moved, t) if s == timerModifiedEarlier { if n := atomic.Xadd(&pp.adjustTimers, -1); int32(n) <= 0 { break loop } } i-- } case timerNoStatus, timerRunning, timerRemoving, timerRemoved, timerMoving: badTimer() case timerWaiting: case timerModifying: osyield() i-- default: badTimer() } } "htmlcode">func addAdjustedTimers(pp *p, moved []*timer) { for _, t := range moved { doaddtimer(pp, t)// 上文有源码解析 if !atomic.Cas(&t.status, timerMoving, timerWaiting) { badTimer() } } }clearDeletedTimers
func clearDeletedTimers(pp *p) { cdel := int32(0) cearlier := int32(0) to := 0 changedHeap := false timers := pp.timers nextTimer: for _, t := range timers { for { switch s := atomic.Load(&t.status); s { case timerWaiting: if changedHeap { timers[to] = t siftupTimer(timers, to) } to++ continue nextTimer case timerModifiedEarlier, timerModifiedLater: // 将 timer 状态调整成 timeWaiting,将其放至其正确的执行时间位置 if atomic.Cas(&t.status, s, timerMoving) { t.when = t.nextwhen timers[to] = t siftupTimer(timers, to) to++ changedHeap = true if !atomic.Cas(&t.status, timerMoving, timerWaiting) { badTimer() } if s == timerModifiedEarlier { cearlier++ } continue nextTimer } case timerDeleted: // 将 timerDeleted 转变成 timerRemoved,然后从 timers 堆中删掉(在当前函数后面可以看出) if atomic.Cas(&t.status, s, timerRemoving) { t.pp = 0 cdel++ if !atomic.Cas(&t.status, timerRemoving, timerRemoved) { badTimer() } changedHeap = true continue nextTimer } case timerModifying: osyield() case timerNoStatus, timerRemoved: badTimer() case timerRunning, timerRemoving, timerMoving: badTimer() default: badTimer() } } } "htmlcode">func (t *Ticker) Stop() { stopTimer(&t.r) } "htmlcode">func (t *Timer) Reset(d Duration) bool { if t.r.f == nil { panic("time: Reset called on uninitialized Timer") } w := when(d) active := stopTimer(&t.r) // 这里我们上面源码解释过了,即将当前的 timer 的 status 设置成 timerDeleted resetTimer(&t.r, w) return active } func resettimer(t *timer, when int64) { modtimer(t, when, t.period, t.f, t.arg, t.seq) } func modtimer(t *timer, when, period int64, f func(interface{}, uintptr), arg interface{}, seq uintptr) { if when < 0 { when = maxWhen } status := uint32(timerNoStatus) wasRemoved := false var mp *m loop: for { // 主要的目的就是将当前的 timer 的状态设置成 timerModifying switch status = atomic.Load(&t.status); status { case timerWaiting, timerModifiedEarlier, timerModifiedLater: mp = acquirem() if atomic.Cas(&t.status, status, timerModifying) { break loop } releasem(mp) case timerNoStatus, timerRemoved: mp = acquirem() if atomic.Cas(&t.status, status, timerModifying) { wasRemoved = true break loop } releasem(mp) case timerDeleted: mp = acquirem() if atomic.Cas(&t.status, status, timerModifying) { atomic.Xadd(&t.pp.ptr().deletedTimers, -1) break loop } releasem(mp) case timerRunning, timerRemoving, timerMoving: osyield() case timerModifying: osyield() default: badTimer() } } t.period = period t.f = f t.arg = arg t.seq = seq if wasRemoved { // 如果是已经被移除的,则要重新加回到 timers 中,且状态变更为 timerWaiting t.when = when pp := getg().m.p.ptr() lock(&pp.timersLock) doaddtimer(pp, t) unlock(&pp.timersLock) if !atomic.Cas(&t.status, timerModifying, timerWaiting) { badTimer() } releasem(mp) wakeNetPoller(when) } else { t.nextwhen = when newStatus := uint32(timerModifiedLater) if when < t.when { //判断这次新的时间是老的时间的前还是后 newStatus = timerModifiedEarlier } adjust := int32(0) if status == timerModifiedEarlier { adjust-- } if newStatus == timerModifiedEarlier { adjust++ } if adjust != 0 { atomic.Xadd(&t.pp.ptr().adjustTimers, adjust) } if !atomic.Cas(&t.status, timerModifying, newStatus) { // 将当前 timer 设置成 timerModifiedEarlier/timerModifiedEarlier badTimer() } releasem(mp) if newStatus == timerModifiedEarlier { wakeNetPoller(when) } } }到此这篇关于Golang 定时器(Timer 和 Ticker),这篇文章就够了的文章就介绍到这了,更多相关Golang 定时器内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!
标签:Golang,定时器
圆月山庄资源网 Design By www.vgjia.com广告合作:本站广告合作请联系QQ:858582 申请时备注:广告合作(否则不回)
免责声明:本站文章均来自网站采集或用户投稿,网站不提供任何软件下载或自行开发的软件! 如有用户或公司发现本站内容信息存在侵权行为,请邮件告知! 858582#qq.com圆月山庄资源网 Design By www.vgjia.com暂无评论...更新日志
2024年11月15日2024年11月15日
- 黄乙玲1988-无稳定的爱心肝乱糟糟[日本东芝1M版][WAV+CUE]
- 群星《我们的歌第六季 第3期》[320K/MP3][70.68MB]
- 群星《我们的歌第六季 第3期》[FLAC/分轨][369.48MB]
- 群星《燃!沙排少女 影视原声带》[320K/MP3][175.61MB]
- 乱斗海盗瞎6胜卡组推荐一览 深暗领域乱斗海盗瞎卡组分享
- 炉石传说乱斗6胜卡组分享一览 深暗领域乱斗6胜卡组代码推荐
- 炉石传说乱斗本周卡组合集 乱斗模式卡组最新推荐
- 佟妍.2015-七窍玲珑心【万马旦】【WAV+CUE】
- 叶振棠陈晓慧.1986-龙的心·俘虏你(2006复黑限量版)【永恒】【WAV+CUE】
- 陈慧琳.1998-爱我不爱(国)【福茂】【WAV+CUE】
- 咪咕快游豪礼放送,百元京东卡、海量欢乐豆就在咪咕咪粉节!
- 双11百吋大屏焕新“热”,海信AI画质电视成最大赢家
- 海信电视E8N Ultra:真正的百吋,不止是大!
- 曾庆瑜1990-曾庆瑜历年精选[派森][WAV+CUE]
- 叶玉卿1999-深情之选[飞图][WAV+CUE]