golang timer源码解析

387 阅读5分钟

创建

  1. 新建TimerTicker的源码如下,两者基本结构一致。只是TickerruntimeTimer中有一个非零属性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
    }
    
  2. 新建TimerTicker的时候,都使用了函数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放到的位置是在ptimers上,是一个*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任务。

    1. schedule
    2. findrunnable
    3. stealWork

    修改

    1. 更新和修改timer只是修改timer状态,使用cas来获得修改timer、读取timer属性、删除timer的权利。
      1. cas成功设置timer状态为timerModifying表示获得修改timer或者读取timer属性的权利
      2. cas成功设置timer状态为timerRemoving表示获得从ptimer堆中删除timer的权利
    2. 统一在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
    }