golang time包下定时器的实现方法

2019-11-10 11:23:07丽君

func startTimer(t *timer) {
 if raceenabled {
 racerelease(unsafe.Pointer(t))
 }
 addtimer(t)
}

func addtimer(t *timer) {
 lock(&timers.lock)
 addtimerLocked(t)
 unlock(&timers.lock)
}

上面的代码为了看着方便,我将他们都放在一起

下面代码都写出部分注释

// 使用锁将计时器添加到堆中
// 如果是第一次运行此方法则启动timerproc
func addtimerLocked(t *timer) {
 if t.when < 0 {
 t.when = 1<<63 - 1
 }
 // t.i i是定时任务数组中的索引
 // 将新的定时任务追加到定时任务数组队尾
 t.i = len(timers.t)
 timers.t = append(timers.t, t)
 // 使用数组实现的四叉树最小堆根据when(到期时间)进行排序
 siftupTimer(t.i)
 // 如果t.i 索引为0
 if t.i == 0 {
 if timers.sleeping {
 // 如果还在sleep就唤醒
 timers.sleeping = false
 // 这里基于OS的同步,并进行OS系统调用
 // 在timerproc()使goroutine从睡眠状态恢复
 notewakeup(&timers.waitnote)
 }
 if timers.rescheduling {
 timers.rescheduling = false
 // 如果没有定时器,timerproc()与goparkunlock共同sleep
 // goready这里特殊说明下,在线程创建的堆栈,它比goroutine堆栈大。
 // 函数不能增长堆栈,同时不能被调度器抢占
 goready(timers.gp, 0)
 }
 }
 if !timers.created {
 timers.created = true
 go timerproc() //这里只有初始化一次
 }
}

// Timerproc运行时间驱动的事件。
// 它sleep到计时器堆中的下一个。
// 如果addtimer插入一个新的事件,它会提前唤醒timerproc。
func timerproc() {
 timers.gp = getg()
 for {
 lock(&timers.lock)
 timers.sleeping = false
 now := nanotime()
 delta := int64(-1)
 for {
 if len(timers.t) == 0 {
 delta = -1
 break
 }
 t := timers.t[0]
 delta = t.when - now
 if delta > 0 {
 break // 时间未到
 }
 if t.period > 0 {
 // 计算下一次时间
        // period被唤醒的间隔
 t.when += t.period * (1 + -delta/t.period)
 siftdownTimer(0)
 } else {
 // remove from heap
 last := len(timers.t) - 1
 if last > 0 {
  timers.t[0] = timers.t[last]
  timers.t[0].i = 0
 }
 timers.t[last] = nil
 timers.t = timers.t[:last]
 if last > 0 {
  siftdownTimer(0)
 }
 t.i = -1 // 标记移除
 }
 f := t.f
 arg := t.arg
 seq := t.seq
 unlock(&timers.lock)
 if raceenabled {
 raceacquire(unsafe.Pointer(t))
 }
 f(arg, seq)
 lock(&timers.lock)
 }
 if delta < 0 || faketime > 0 {
 // 没有定时器,把goroutine sleep。
 timers.rescheduling = true
 // 将当前的goroutine放入等待状态并解锁锁。
 // goroutine也可以通过呼叫goready(gp)来重新运行。
 goparkunlock(&timers.lock, "timer goroutine (idle)", traceEvGoBlock, 1)
 continue
 }
 // At least one timer pending. Sleep until then.
 timers.sleeping = true
 timers.sleepUntil = now + delta
 // 重置
 noteclear(&timers.waitnote)
 unlock(&timers.lock)
 // 使goroutine进入睡眠状态,直到notewakeup被调用,
 // 通过notewakeup 唤醒
 notetsleepg(&timers.waitnote, delta)
 }
}

golang使用最小堆(最小堆是满足除了根节点以外的每个节点都不小于其父节点的堆)实现的定时器。golang []*timer结构如下: