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 使用上差别如应用实例所示。