go 语言 timer 与 ticker 应用实例与解析

Go里提供了两种定时器:Timer(到达指定时间触发且只触发一次)和 Ticker(间隔特定时间触发)。

Timer和Ticker的实现几乎一样,Ticker相对复杂一些,这里讲述一下Ticker是如何实现的。

让我们先来看一下如何使用Ticker

package main

import(
	"fmt"
	"time"
)

func main(){
	d := time.Duration(time.Second*1)
	t := time.NewTicker(d)
	defer t.Stop()

	for {
		<- t.C						//读取 channel 内信息,管道有值则执行,无值则阻塞。
		fmt.Println("timeout...")
	}
}

代码很简洁,给开发者提供了巨大的便利。那GoLang是如何实现这个功能的呢?

调用NewTicker可以生成Ticker,关于这个函数有四点需要说明

  • NewTicker主要作用之一是初始化
  • NewTicker中的时间是以纳秒为单位的,when返回的从当前时间+d的纳秒值,d必须为正值
  • Ticker结构体中包含channel,sendTime是个function,逻辑为用select等待c被赋值
  • 神秘的startTimer函数,揭示channel、sendTime是如何关联的

time/tick.go的Ticker数据结构

package time
import "errors"

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

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


// Tick is a convenience wrapper for NewTicker providing access to the ticking
// channel only. While Tick is useful for clients that have no need to shut down
// the Ticker, be aware that without a way to shut it down the underlying
// Ticker cannot be recovered by the garbage collector; it "leaks".
// Unlike NewTicker, Tick will return nil if d <= 0.
func Tick(d Duration) <-chan Time {
        if d <= 0 {
                return nil
        }
        return NewTicker(d).C
}
   

time/sleep.go的runtimeTimer

// Interface to timers implemented in package runtime.
// Must be in sync with ../runtime/time.go:/^type timer
type runtimeTimer struct {
        pp       uintptr
        when     int64
        period   int64
        f        func(interface{}, uintptr) // NOTE: must not be closure
        arg      interface{}
        seq      uintptr
        nextwhen int64
        status   uint32
}

// when is a helper function for setting the 'when' field of a runtimeTimer.
// It returns what the time will be, in nanoseconds, Duration d in the future.
// If d is negative, it is ignored. If the returned value would be less than
// zero because of an overflow, MaxInt64 is returned.
func when(d Duration) int64 {
        if d <= 0 {
                return runtimeNano()
        }
        t := runtimeNano() + int64(d)
        if t < 0 {
                t = 1<<63 - 1 // math.MaxInt64
        }
        return t
}

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

runtime/time.go的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 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 never be negative; otherwise runtimer will overflow
        // during its delta calculation and never expire other runtime timers.
        if t.when < 0 {
                t.when = maxWhen
        }
        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)
}

看到 startTimer() 函数, 和ticker应用接口;ticker定时器指定间隔就调用sendTime()函数,向 channel中写入一次Now时间。

timer 应用实例

package main
import (
	"fmt"
	"time"
)

func main() {
	d := time.Duration(1 * time.Second)
	t := time.NewTimer(d)
	defer t.Stop()

	for {
		<- t.C
		fmt.Println("timerout...")

		//must be reset timer
		t.Reset(1 * time.Second)
	}
}

timer 与 ticker 使用上差别如应用实例所示。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值