golang中timer和ticker 都属于time包
timer是一次性定时器,执行完一次就结束了,ticker是周期性定时器,周而复始的执行。二者在数据结构上完全一样,都是一个对外的channel + 对内的runtimeTimer。
type Timer struct {
C <-chan Time
r runtimeTimer
}
type Ticker struct {
C <-chan Time // The channel on which the ticks are delivered.
r runtimeTimer
}
一:Timer常用的使用方式:
1:timer = time.NewTimer(1 * time.Second) timer.Stop() timer.Reset()
2: <-time.After(1 * time.Second)
3:time.AfterFunc(1 * time.Second,callback)
二:Ticker常用的使用方式:
1:ticker = time.NewTicker(1 * time.Second) ticker.Stop() for range ticker.C
2:<-time.Tick(1 * time.Second)
三:runtimeTimer结构
type runtimeTimer struct {
tb *timersBucket // 存储当前定时器的数组地址
i int // 存储当前定时器的数组下标
when int64 // 当前定时器触发时间
period int64 // 当前定时器周期触发间隔
f func(interface{}, uintptr) // 定时器触发时执行的函数
arg interface{} // 定时器触发时执行函数传递的参数一
seq uintptr // 定时器触发时执行函数传递的参数二(该参数只在网络收发场景下使用)
}
timer和ticker 唯一的区别就是在period上,为timer时其值恒为0,为ticker时其值就是循环周期。
四:实现原理
func NewTimer(d Duration) *Timer {
c := make(chan Time, 1) // 创建一个管道
t := &Timer{ // 构造Timer数据结构
C: c, // 新创建的管道
r: runtimeTimer{
when: when(d), // 触发时间
f: sendTime, // 触发后执行函数sendTime
arg: c, // 触发后执行函数sendTime时附带的参数
},
}
startTimer(&t.r) // 此处启动定时器,只是把runtimeTimer放到系统协程的堆中,由系统协程维护
return t
}
func NewTicker(d Duration) *Ticker {
if d <= 0 {
panic(errors.New("non-positive interval for NewTicker"))
}
c := make(chan Time, 1)
t := &Ticker{
C: c,
r: runtimeTimer{
when: when(d),
period: int64(d), // Ticker跟Timer的重要区就是提供了period这个参数,据此决定timer是一次性的,还是周期性的
f: sendTime,
arg: c,
},
}
startTimer(&t.r)
return t
}
func sendTime(c interface{}, seq uintptr) {
select {
case c.(chan Time) <- Now():
default:
}
}
sendTime 表示定时器时间到了执行的api。
????????timer触发时不会阻塞,因为channel是有缓冲的,把当前的时间塞进去就完事儿了,但是ticker不太一样,ticker 是不断的塞 万一对方没有及时把时间从channel中读走,这个时候sendTime只能执行default了,这次塞数据的操作会被丢弃。
startTimer 就是把当前的timer or ticker 交给系统协程。
? ? ? ? 系统有专门的协程管理着所有的timer or ticker,并不是每一个timer or ticker就有一个对应的协程,如果真的是那样的,启动几百个定时器 岂不是要有几百个协程跟着服务 这样效率也太低了。
????????系统协程有64个,每个系统协程通过四叉堆管理着其下属的定时器,添加/删除都会涉及到堆的调整,
|