Timer 实现

67 阅读2分钟
  • p.timers 四叉堆,小顶堆 (四叉堆和二叉堆本质上没有区别,整体上层数更低,且时间复杂度从 O(log2N)降到O(log4N))
  • addtimer 会调用 wakeNetPoller(when)
    • wakeNetPoller wakes up the thread sleeping in the network poller if it isn't going to wake up before the when argument
  • findrunnable 里 会使用 带有超时的阻塞 方法调用epollwait
    • list, delta := netpoll(delay) // block until new work is available
// The Timer type represents a single event.
// When the Timer expires, the current time will be sent on C,
// unless the Timer was created by AfterFunc.
// A Timer must be created with NewTimer or AfterFunc.
type Timer struct {
   C <-chan Time
   r runtimeTimer
}
type runtimeTimer struct {
   pp       uintptr
   when     int64
   period   int64
   f        func(any, uintptr) // NOTE: must not be closure
   arg      any
   seq      uintptr
   nextwhen int64
   status   uint32
}

NewTimer

at least duration d.

// NewTimer creates a new Timer that will send
// the current time on its channel after at least duration d.
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
}
// 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)// cleantimers cleans up the head of the timer queue.

   doaddtimer(pp, t)//adds t to the current P's heap.(四叉堆)

   unlock(&pp.timersLock)

   wakeNetPoller(when)

   releasem(mp)
}
// wakeNetPoller wakes up the thread sleeping in the network poller if it isn't
// going to wake up before the when argument; or it wakes an idle P to service
// timers and the network poller if there isn't one already.
func wakeNetPoller(when int64) {
   if atomic.Load64(&sched.lastpoll) == 0 {
      // In findrunnable we ensure that when polling the pollUntil
      // field is either zero or the time to which the current
      // poll is expected to run. This can have a spurious wakeup
      // but should never miss a wakeup.
      pollerPollUntil := int64(atomic.Load64(&sched.pollUntil))
      if pollerPollUntil == 0 || pollerPollUntil > when {
         netpollBreak()
      }
   } else {
      // There are no threads in the network poller, try to get
      // one there so it can handle new timers.
      if GOOS != "plan9" { // Temporary workaround - see issue #42303.
         wakep()
      }
   }
}
// Tries to add one more P to execute G's.
// Called when a G is made runnable (newproc, ready).
func wakep() {
   if atomic.Load(&sched.npidle) == 0 {
      return
   }
   // be conservative about spinning threads
   if atomic.Load(&sched.nmspinning) != 0 || !atomic.Cas(&sched.nmspinning, 0, 1) {
      return
   }
   startm(nil, true)
}