创建
-
新建
Timer
和Ticker
的源码如下,两者基本结构一致。只是Ticker
的runtimeTimer
中有一个非零属性period
,表示是周期性的tick。func NewTimer(d Duration) *Timer { c := make(chan Time, 1) t := &Timer{ C: c, r: runtimeTimer{ when: when(d), f: sendTime, arg: c, }, } startTimer(&t.r) return t } func NewTicker(d Duration) *Ticker { if d <= 0 { panic(errors.New("non-positive interval for NewTicker")) } // Give the channel a 1-element time buffer. // If the client falls behind while reading, we drop ticks // on the floor until the client catches up. c := make(chan Time, 1) t := &Ticker{ C: c, r: runtimeTimer{ when: when(d), period: int64(d), f: sendTime, arg: c, }, } startTimer(&t.r) return t }
-
新建
Timer
和Ticker
的时候,都使用了函数func startTimer(*runtimeTimer)
,此部分详细代码在runtime中。具体内容如下// startTimer adds t to the timer heap. //go:linkname startTimer time.startTimer func startTimer(t *timer) { if raceenabled { racerelease(unsafe.Pointer(t)) } addtimer(t) } // addtimer adds a timer to the current P. // This should only be called with a newly created timer. // That avoids the risk of changing the when field of a timer in some P's heap, // which could cause the heap to become unsorted. func addtimer(t *timer) { // when must be positive. A negative value will cause runtimer to // overflow during its delta calculation and never expire other runtime // timers. Zero will cause checkTimers to fail to notice the timer. if t.when <= 0 { throw("timer when must be positive") } if t.period < 0 { throw("timer period must be non-negative") } if t.status != timerNoStatus { throw("addtimer called with initialized timer") } t.status = timerWaiting when := t.when // Disable preemption while using pp to avoid changing another P's heap. mp := acquirem() pp := getg().m.p.ptr() lock(&pp.timersLock) cleantimers(pp) doaddtimer(pp, t) unlock(&pp.timersLock) wakeNetPoller(when) releasem(mp) }
实际把
timer
放到的位置是在p
的timers
上,是一个*timer
数组,按照堆的性质顺序排列。为了让堆比较矮胖,使用了四叉堆(每个节点有四个子节点,每个节点的运行时间小于等于所有子节点的当前时间)。由于是新增*timer
,所以添加到数组尾部,进行siftupTimer
调整尾部元素位置,满足堆性质即可。// doaddtimer adds t to the current P's heap. // The caller must have locked the timers for pp. func doaddtimer(pp *p, t *timer) { // Timers rely on the network poller, so make sure the poller // has started. if netpollInited == 0 { netpollGenericInit() } if t.pp != 0 { throw("doaddtimer: P already set in timer") } t.pp.set(pp) 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) }
运行
实际运行
timer
的函数是runtimer
,判断堆定元素的执行时间when
是否小于等于当前时间now
,如果大于说明还没有可以运行的元素,此时直接退出,并返回堆顶元素的运行时间。其他状态后面讲timer
更新的时候再补充。// runtimer examines the first timer in timers. If it is ready based on now, // it runs the timer and removes or updates it. // Returns 0 if it ran a timer, -1 if there are no more timers, or the time // when the first timer should run. // The caller must have locked the timers for pp. // If a timer is run, this will temporarily unlock the timers. //go:systemstack func runtimer(pp *p, now int64) int64 { for { t := pp.timers[0] if t.pp.ptr() != pp { throw("runtimer: bad p") } switch s := atomic.Load(&t.status); s { case timerWaiting: if t.when > now { // Not ready to run. return t.when } if !atomic.Cas(&t.status, s, timerRunning) { continue } // Note that runOneTimer may temporarily unlock // pp.timersLock. runOneTimer(pp, t, now) return 0 case timerDeleted: if !atomic.Cas(&t.status, s, timerRemoving) { continue } dodeltimer0(pp) if !atomic.Cas(&t.status, timerRemoving, timerRemoved) { badTimer() } atomic.Xadd(&pp.deletedTimers, -1) if len(pp.timers) == 0 { return -1 } case timerModifiedEarlier, timerModifiedLater: if !atomic.Cas(&t.status, s, timerMoving) { continue } t.when = t.nextwhen dodeltimer0(pp) doaddtimer(pp, t) if !atomic.Cas(&t.status, timerMoving, timerWaiting) { badTimer() } case timerModifying: // Wait for modification to complete. osyield() case timerNoStatus, timerRemoved: // Should not see a new or inactive timer on the heap. badTimer() case timerRunning, timerRemoving, timerMoving: // These should only be set when timers are locked, // and we didn't do it. badTimer() default: badTimer() } } }
但是这个
runtimer
触发时机是何时?调用此函数的是checkTimers
func checkTimers(pp *p, now int64) (rnow, pollUntil int64, ran bool) { // If it's not yet time for the first timer, or the first adjusted // timer, then there is nothing to do. next := int64(atomic.Load64(&pp.timer0When)) nextAdj := int64(atomic.Load64(&pp.timerModifiedEarliest)) if next == 0 || (nextAdj != 0 && nextAdj < next) { next = nextAdj } if next == 0 { // No timers to run or adjust. return now, 0, false } if now == 0 { now = nanotime() } if now < next { // Next timer is not ready to run, but keep going // if we would clear deleted timers. // This corresponds to the condition below where // we decide whether to call clearDeletedTimers. if pp != getg().m.p.ptr() || int(atomic.Load(&pp.deletedTimers)) <= int(atomic.Load(&pp.numTimers)/4) { return now, next, false } } lock(&pp.timersLock) if len(pp.timers) > 0 { adjusttimers(pp, now) for len(pp.timers) > 0 { // Note that runtimer may temporarily unlock // pp.timersLock. <font color='red'>here</font> if tw := runtimer(pp, now); tw != 0 { if tw > 0 { pollUntil = tw } break } ran = true } } // If this is the local P, and there are a lot of deleted timers, // clear them out. We only do this for the local P to reduce // lock contention on timersLock. if pp == getg().m.p.ptr() && int(atomic.Load(&pp.deletedTimers)) > len(pp.timers)/4 { clearDeletedTimers(pp) } unlock(&pp.timersLock) return now, pollUntil, ran }
checkTimers
的调用位置有如下三个,均在runtime/proc.go
,就不具体贴代码了。在每个p
的当前goroutine
执行完成之后,都会查找本地timers
中是否有可以执行的timer
任务(优先级高于执行p
的本地任务列表), 所以创建好的Timer
或者Ticker
最好都做一下Stop
。另外还会在stealWork
的时候查看其它p
上可能到期的timer
任务。schedule
findrunnable
stealWork
修改
- 更新和修改
timer
只是修改timer
状态,使用cas
来获得修改timer
、读取timer
属性、删除timer
的权利。cas
成功设置timer
状态为timerModifying
表示获得修改timer
或者读取timer
属性的权利cas
成功设置timer
状态为timerRemoving
表示获得从p
的timer
堆中删除timer
的权利
- 统一在
checkTimers
通过获取timersLock
锁,对全部修改或者删除状态的timer
在堆上调整位置
func deltimer(t *timer) bool { for { switch s := atomic.Load(&t.status); s { case timerWaiting, timerModifiedLater: // Prevent preemption while the timer is in timerModifying. // This could lead to a self-deadlock. See #38070. mp := acquirem() if atomic.Cas(&t.status, s, timerModifying) { // Must fetch t.pp before changing status, // as cleantimers in another goroutine // can clear t.pp of a timerDeleted timer. tpp := t.pp.ptr() if !atomic.Cas(&t.status, timerModifying, timerDeleted) { badTimer() } releasem(mp) atomic.Xadd(&tpp.deletedTimers, 1) // Timer was not yet run. return true } else { releasem(mp) } case timerModifiedEarlier: // Prevent preemption while the timer is in timerModifying. // This could lead to a self-deadlock. See #38070. mp := acquirem() if atomic.Cas(&t.status, s, timerModifying) { // Must fetch t.pp before setting status // to timerDeleted. tpp := t.pp.ptr() if !atomic.Cas(&t.status, timerModifying, timerDeleted) { badTimer() } releasem(mp) atomic.Xadd(&tpp.deletedTimers, 1) // Timer was not yet run. return true } else { releasem(mp) } case timerDeleted, timerRemoving, timerRemoved: // Timer was already run. return false case timerRunning, timerMoving: // The timer is being run or moved, by a different P. // Wait for it to complete. osyield() case timerNoStatus: // Removing timer that was never added or // has already been run. Also see issue 21874. return false case timerModifying: // Simultaneous calls to deltimer and modtimer. // Wait for the other call to complete. osyield() default: badTimer() } } } // modtimer modifies an existing timer. // This is called by the netpoll code or time.Ticker.Reset or time.Timer.Reset. // Reports whether the timer was modified before it was run. func modtimer(t *timer, when, period int64, f func(any, uintptr), arg any, seq uintptr) bool { if when <= 0 { throw("timer when must be positive") } if period < 0 { throw("timer period must be non-negative") } status := uint32(timerNoStatus) wasRemoved := false var pending bool var mp *m loop: for { switch status = atomic.Load(&t.status); status { case timerWaiting, timerModifiedEarlier, timerModifiedLater: // Prevent preemption while the timer is in timerModifying. // This could lead to a self-deadlock. See #38070. mp = acquirem() if atomic.Cas(&t.status, status, timerModifying) { pending = true // timer not yet run break loop } releasem(mp) case timerNoStatus, timerRemoved: // Prevent preemption while the timer is in timerModifying. // This could lead to a self-deadlock. See #38070. mp = acquirem() // Timer was already run and t is no longer in a heap. // Act like addtimer. if atomic.Cas(&t.status, status, timerModifying) { wasRemoved = true pending = false // timer already run or stopped break loop } releasem(mp) case timerDeleted: // Prevent preemption while the timer is in timerModifying. // This could lead to a self-deadlock. See #38070. mp = acquirem() if atomic.Cas(&t.status, status, timerModifying) { atomic.Xadd(&t.pp.ptr().deletedTimers, -1) pending = false // timer already stopped break loop } releasem(mp) case timerRunning, timerRemoving, timerMoving: // The timer is being run or moved, by a different P. // Wait for it to complete. osyield() case timerModifying: // Multiple simultaneous calls to modtimer. // Wait for the other call to complete. osyield() default: badTimer() } } t.period = period t.f = f t.arg = arg t.seq = seq if wasRemoved { 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 { // The timer is in some other P's heap, so we can't change // the when field. If we did, the other P's heap would // be out of order. So we put the new when value in the // nextwhen field, and let the other P set the when field // when it is prepared to resort the heap. t.nextwhen = when newStatus := uint32(timerModifiedLater) if when < t.when { newStatus = timerModifiedEarlier } tpp := t.pp.ptr() if newStatus == timerModifiedEarlier { updateTimerModifiedEarliest(tpp, when) } // Set the new status of the timer. if !atomic.Cas(&t.status, timerModifying, newStatus) { badTimer() } releasem(mp) // If the new status is earlier, wake up the poller. if newStatus == timerModifiedEarlier { wakeNetPoller(when) } } return pending }