一文搞懂GoLang定时器实现原理

简介工作中经常有定时执行某些代码块的需求 , 如果是php代码 , 一般写个脚本 , 然后用Cron实现 。
Go里提供了两种定时器:Timer(到达指定时间触发且只触发一次)和 Ticker(间隔特定时间触发) 。
Timer和Ticker的实现几乎一样 , Ticker相对复杂一些 , 这里主要讲述一下Ticker是如何实现的 。
让我们先来看一下如何使用Ticker
//创建Ticker , 设置多长时间触发一次ticker := time.NewTicker(time.Second * 10)go func() {   for range ticker.C { //遍历ticker.C , 如果有值 , 则会执行do someting , 否则阻塞      //do someting   }}()代码很简洁 , 给开发者提供了巨大的便利 。那GoLang是如何实现这个功能的呢?
原理NewTickertime/tick.go的NewTicker函数:
调用NewTicker可以生成Ticker , 关于这个函数有四点需要说明

  1. NewTicker主要作用之一是初始化
  2. NewTicker中的时间是以纳秒为单位的 , when返回的从当前时间+d的纳秒值 , d必须为正值
  3. Ticker结构体中包含channel , sendTime是个function , 逻辑为用select等待c被赋值
  4. 神秘的startTimer函数 , 揭示channel、sendTime是如何关联的
// NewTicker returns a new Ticker containing a channel that will send the// time with a period specified by the duration argument.// It adjusts the intervals or drops 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}time/tick.go的Ticker数据结构
// A Ticker holds a channel that delivers `ticks' of a clock// at intervals.type Ticker struct {   C <-chan Time // The channel on which the ticks are delivered.   r runtimeTimer}time/sleep.go的runtimeTimer
// Interface to timers implemented in package runtime.// Must be in sync with ../runtime/time.go:/^type timertype runtimeTimer struct {   tb uintptr   i  int   when   int64   period int64   f      func(interface{}, uintptr) // NOTE: must not be closure   arg    interface{}   seq    uintptr}time/sleep.go的sendTime
func sendTime(c interface{}, seq uintptr) {   // Non-blocking send of time on c.   // Used in NewTimer, it cannot block anyway (buffer).   // Used in NewTicker, dropping sends on the floor is   // the desired behavior when the reader gets behind,   // because the sends are periodic.   select {   case c.(chan Time) <- Now():   default:   }}time/sleep.go的startTimer
func startTimer(*runtimeTimer)func stopTimer(*runtimeTimer) boolstartTimer看完上面的代码 , 大家内心是不是能够猜出是怎么实现的?
有一个机制保证时间到了时 , sendTime被调用 , 此时channel会被赋值 , 调用ticker.C的位置解除阻塞 , 执行指定的逻辑 。
让我们看一下GoLang是不是这样实现的 。
追踪代码的时候我们发现在time包里的startTimer , 只是一个声明 , 那真正的实现在哪里?
runtime/time.go的startTimer
此处使用go的隐藏技能go:linkname引导编译器将当前(私有)方法或者变量在编译时链接到指定的位置的方法或者变量 。另外timer和runtimeTimer的结构是一致的 , 所以程序运行正常 。
//startTimer将new的timer对象加入timer的堆数据结构中//startTimer adds t to the timer heap.//go:linkname startTimer time.startTimerfunc startTimer(t *timer) {   if raceenabled {      racerelease(unsafe.Pointer(t))   }   addtimer(t)}runtime/time.go的addtimer
func addtimer(t *timer) {   tb := t.assignBucket()   lock(&tb.lock)   ok := tb.addtimerLocked(t)   unlock(&tb.lock)   if !ok {      badTimer()   }}


推荐阅读