sleep、 ticker、timers

128 阅读4分钟

sleep

定义

// Sleep pauses the current goroutine for at least the duration d. // A negative or zero duration causes Sleep to return immediately.

func Sleep(d Duration)

timeSleep

以下来自src/runtime/time.go

设置timer,然后gopark,schedule()里面会调用 checkTimers,到时间了会把这个goroutine唤醒

g有一个timer字段,cached timer for time.Sleep

// timeSleep puts the current goroutine to sleep for at least ns nanoseconds.
//go:linkname timeSleep time.Sleep
func timeSleep(ns int64) {
   if ns <= 0 {
      return
   }

   gp := getg()
   t := gp.timer
   if t == nil {
      t = new(timer)
      gp.timer = t
   }
   t.f = goroutineReady
   t.arg = gp
   t.nextwhen = nanotime() + ns
   if t.nextwhen < 0 { // check for overflow.
      t.nextwhen = maxWhen
   }
   gopark(resetForSleep, unsafe.Pointer(t), waitReasonSleep, traceEvGoSleep, 1)
}

使用

time.Sleep(time.Second * 1)

ticker

不要用time.Tick, 要用time.NewTicker

func main() {
    // ...create abort channel...

    fmt.Println("Commencing countdown.  Press return to abort.")
    tick := time.Tick(1 * time.Second)
    for countdown := 10; countdown > 0; countdown-- {
        fmt.Println(countdown)
        select {
        case <-tick:
            // Do nothing.
        case <-abort:
            fmt.Println("Launch aborted!")
            return
        }
    }
    launch()
}

time.Tick函数表现得好像它创建了一个在循环中调用time.Sleep的goroutine,每次被唤醒时发送一个事件。当countdown函数返回时,它会停止从tick中接收事件,但是ticker这个goroutine还依然存活,继续徒劳地尝试向channel中发送值,然而这时候已经没有其它的goroutine会从该channel中接收值了——这被称为goroutine泄露(§8.4.4)。

Tick函数挺方便,但是只有当程序整个生命周期都需要这个时间时我们使用它才比较合适。否则的话,我们应该使用下面的这种模式:

ticker := time.NewTicker(1 * time.Second)
<-ticker.C    // receive from the ticker's channel
ticker.Stop() // cause the ticker's goroutine to terminate

定义

NewTicker returns a new Ticker containing a channel that will send the current time on the channel after each tick. The period of the ticks is specified by the duration argument. The ticker will adjust the time interval or drop ticks to make up for slow receivers. The duration d must be greater than zero; if not, NewTicker will panic. Stop the ticker to release associated resources.

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

type timer struct {
   // If this timer is on a heap, which P's heap it is on.
   // puintptr rather than *p to match uintptr in the versions
   // of this struct defined in other packages.
   pp puintptr

   // Timer wakes up at when, and then at when+period, ... (period > 0 only)
   // each time calling f(arg, now) in the timer goroutine, so f must be
   // a well-behaved function and not block.
   //
   // when must be positive on an active timer.
   when   int64
   period int64
   f      func(any, uintptr)
   arg    any
   seq    uintptr

   // What to set the when field to in timerModifiedXX status.
   nextwhen int64

   // The status field holds one of the values below.
   status uint32
}

Active timers live in heaps attached to P, in the timers field. Inactive timers live there too temporarily, until they are removed.

startTimer

// 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

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)
}

doaddtimer

// 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)
}

netpollGenericInit

func netpollGenericInit() {
   if atomic.Load(&netpollInited) == 0 {
      lockInit(&netpollInitLock, lockRankNetpollInit)
      lock(&netpollInitLock)
      if netpollInited == 0 {
         netpollinit()
         atomic.Store(&netpollInited, 1)
      }
      unlock(&netpollInitLock)
   }
}

netpollinit

func netpollinit() {
   kq = kqueue()
   if kq < 0 {
      println("runtime: kqueue failed with", -kq)
      throw("runtime: netpollinit failed")
   }
   closeonexec(kq)
   r, w, errno := nonblockingPipe()
   if errno != 0 {
      println("runtime: pipe failed with", -errno)
      throw("runtime: pipe failed")
   }
   ev := keventt{
      filter: _EVFILT_READ,
      flags:  _EV_ADD,
   }
   *(*uintptr)(unsafe.Pointer(&ev.ident)) = uintptr(r)
   n := kevent(kq, &ev, 1, nil, 0, nil)
   if n < 0 {
      println("runtime: kevent failed with", -n)
      throw("runtime: kevent failed")
   }
   netpollBreakRd = uintptr(r)
   netpollBreakWr = uintptr(w)
}

siftupTimer

四叉堆

// Heap maintenance algorithms.
// These algorithms check for slice index errors manually.
// Slice index error can happen if the program is using racy
// access to timers. We don't want to panic here, because
// it will cause the program to crash with a mysterious
// "panic holding locks" message. Instead, we panic while not
// holding a lock.

// siftupTimer puts the timer at position i in the right place
// in the heap by moving it up toward the top of the heap.
// It returns the smallest changed index.
func siftupTimer(t []*timer, i int) int {
   if i >= len(t) {
      badTimer()
   }
   when := t[i].when
   if when <= 0 {
      badTimer()
   }
   tmp := t[i]
   for i > 0 {
      p := (i - 1) / 4 // parent
      if when >= t[p].when {
         break
      }
      t[i] = t[p]
      i = p
   }
   if tmp != t[i] {
      t[i] = tmp
   }
   return i
}

wakeNetPoller

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()
      }
   }
}

使用

ticker := time.NewTicker(time.Second * 1)
for {
   select {
   case <-ticker.C:
      c.DeleteExpired()
   case <-j.stop://其他channel的终止信号
      ticker.Stop()
      return
   }
}