UP | HOME

go中的定时器timer

Table of Contents

1 使用方法

golang中定时器的使用接口在time包中,其中最主要两个接口是time.After跟,time.AfterFunc其中第一个After接口返回一个chan Time, 当时间到时可以读出Timer, AfterFunc接受一个方法,当时间到时执行这个方法。

package main

import (
  "time"
  "fmt"
)

func main() {
  a := time.After(2 * time.Second)
  <- a
  fmt.Println("timer receive")

  time.AfterFunc(2 * time.Second, func(){
    fmt.Println("timer receive")
  })
}

After其实跟AfterFunc差不多,只不过其中的func参数是先通道中写数据

2 内部实现

由于After跟AfterFunc差不多,这里主要看看AfterFunc的实现

  //time/sleep.go
 func AfterFunc(d Duration, f func()) *Timer {
    t := &Timer{
      r: runtimeTimer{
    when: when(d),
    f:    goFunc,
    arg:  f,
      },
    }
    startTimer(&t.r)
    return t
  } 
  func goFunc(arg interface{}, seq uintptr) {
      go arg.(func())()
}

AfterFunc很简单,就是把参数封装为runtimeTimer,然后启动timer(把timer添加到队列中), 这部分代码在runtime/time.go中,注意这里goFunc新启动了一个goroutine来执行用户的任务,这样用户的func就不会堵塞timer

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

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

  // Add a timer to the heap and start or kick the timer proc.
  // If the new timer is earlier than any of the others.
  // Timers are locked.
  func addtimerLocked(t *timer) {
    // when must never be negative; otherwise timerproc will overflow
    // during its delta calculation and never expire other runtime·timers.
    if t.when < 0 {
      t.when = 1<<63 - 1
    }
    //添加time到全局timer
    t.i = len(timers.t)
    timers.t = append(timers.t, t)
    //使用最小堆算法维护timer队列
    siftupTimer(t.i)
    //如果是第一个
    if t.i == 0 {
      // siftup moved to top: new earliest deadline.
      //如果在sleep中, 唤醒
      if timers.sleeping {
    timers.sleeping = false
    notewakeup(&timers.waitnote)
      }
      //如果在调度中, 等待
      if timers.rescheduling {
    timers.rescheduling = false
    goready(timers.gp, 0)
      }
    }
    //如果timer还没创建,则创建
    if !timers.created {
      timers.created = true
      go 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]
      //得到剩余时间, 还没到时间就sleep
      delta = t.when - now
      if delta > 0 {
    break
      }
      //如果是周期性的就算下一次时间
      if t.period > 0 {
    // leave in heap but adjust next time to fire
    t.when += t.period * (1 + -delta/t.period)
    //最小堆下沉
    siftdownTimer(0)
      } else {
    // remove from heap
    //删除将要执行的timer,(最小堆算法)
    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 // mark as removed
      }
      f := t.f
      arg := t.arg
      seq := t.seq
      unlock(&timers.lock)
      if raceenabled {
    raceacquire(unsafe.Pointer(t))
      }
      //执行函数调用函数
      f(arg, seq)
      lock(&timers.lock)
    }
    //继续下一个,因为可能下一个timer也到时间了
    if delta < 0 || faketime > 0 {
      // No timers left - put goroutine to sleep.
      timers.rescheduling = true
      goparkunlock(&timers.lock, "timer goroutine (idle)", traceEvGoBlock, 1)
      continue
    }
    // At least one timer pending.  Sleep until then.
    timers.sleeping = true
    noteclear(&timers.waitnote)
    unlock(&timers.lock)
    //没到时间,睡眠delta时间
    notetsleepg(&timers.waitnote, delta)
  }
}

3 其他实现方法

以前也看过erlang的timer的实现,不过erlang的实现是使用时间轮的方式,不同的时间挂在不同的时间刻度上,而golang使用的最小堆维护的一个timer队列。总的来说golang的实现方式比较简单,erlang的方式比较复杂,但是erlang在支持比较大量的timer情况下比较好,效率比较高。而go比较费时,每次操作最坏情况下都是logn(n为时间队列长度).