目录
ticker源码分析
关键源码
ticker核心源码
package time
import "errors"
type Ticker struct {
C <-chan Time
r runtimeTimer
}
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),
f: sendTime,
arg: c,
},
}
startTimer(&t.r)
return t
}
func (t *Ticker) Stop() {
stopTimer(&t.r)
}
startTimer stopTimer sentTime 核心源码
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:
}
}
// startTimer adds t to the timer heap.
//go:linkname startTimer time.startTimer
func startTimer(t *timer) {
if raceenabled {
racerelease(unsafe.Pointer(t))
}
addtimer(t)
}
// stopTimer removes t from the timer heap if it is there.
// It returns true if t was removed, false if t wasn't even there.
//go:linkname stopTimer time.stopTimer
func stopTimer(t *timer) bool {
return deltimer(t)
}
func deltimer(t *timer) bool {
if t.tb == nil {
//若创建了runtimeTimer未加入调度池为false
return false
}
tb := t.tb
//移除runtimeTimer
lock(&tb.lock)
removed, ok := tb.deltimerLocked(t)
unlock(&tb.lock)
if !ok {
badTimer()
}
return removed
}
源码解读
分析
ticker通过startTimer用于在timeproc加入一个调度任务
ticker通过stopTimer用于在timeproc加入去掉调度任务
ticker通过sentTime接收timeproc的调度信号,移除调度任务后并不会关闭ticker通道
ticker起停逻辑为创建并加入调度任务池/从调度任务池删除
注意事项
- ticker必须关闭 ,即底层将runtimeTimer移除系统定时任务池,否则ticker的runtimeTimer持续运行,无法回收。
- ticker关闭后通道C不能关闭,且使用select且会造成阻塞,即ticker只需要将runtimeTimer移除定时任务池,通道C并没有关闭,使用select时将会阻塞在<-C。不关闭C的原因是底层timerporc sendtime概率性出现给关闭的通道发送。
ticker优化关闭思路
组合形式拓展ticker
通过另外通道来打断select阻塞
MyTicker代码
代码
package main
import (
"fmt"
"sync"
"time"
)
const (
YYYYMMDDHHMISS = "2006-01-02 15:04:05"
)
type MyTicker struct {
*time.Ticker //扩展定时器
interval time.Duration //定时周期
fn func() //回调
chn chan bool //关闭信号
status bool //状态 true表示启动 false表示非启动
}
//设置状态值
func (m *MyTicker) setStatus(status bool) {
m.status = status
}
//获取状态值
func (m *MyTicker) getStatus() bool {
return m.status
}
//NewTicker interval秒级周期 fn回调函数
func NewTicker(interval int64, fn func()) *MyTicker {
m := &MyTicker{
interval: time.Duration(interval) * time.Second,
fn: fn,
chn: make(chan bool),
}
return m
}
//Stop 关闭定时器
func (m *MyTicker) Stop() {
fmt.Println(time.Now().Format(YYYYMMDDHHMISS), "关闭定时器...") //打印
if !m.getStatus() {
fmt.Println(time.Now().Format(YYYYMMDDHHMISS), "定时已经关闭") //打印
return
}
//发送关闭信号
m.chn <- true
}
//Stop 启动定时器
func (m *MyTicker) Start() {
fmt.Println(time.Now().Format(YYYYMMDDHHMISS), "启动定时器...") //打印
if m.getStatus() {
fmt.Println(time.Now().Format(YYYYMMDDHHMISS), "定时已经开启") //打印
return
}
//启动携程监听timerporc调度 tiker 以及 自定义关闭信号
go func() {
//启动 ticker
m.Ticker = time.NewTicker(m.interval)
m.setStatus(true)
fmt.Println(time.Now().Format(YYYYMMDDHHMISS), "定时启动") //打印
//跳出for循环时 关闭ticker
defer m.Ticker.Stop()
defer m.setStatus(false)
defer fmt.Println(time.Now().Format(YYYYMMDDHHMISS), "定时关闭")//打印
//阻塞监听调度信号
for {
select {
case <-m.Ticker.C:
//监听ticker 信号 调用任务
go m.fn()
case <-m.chn:
//监听信号 跳出for 执行defer
return
default:
}
}
}()
}
func main() {
ticker := NewTicker(1, func() {
fmt.Println(time.Now().Format(YYYYMMDDHHMISS), "定时器执行")
})
ticker.Stop() //检测 stop未启动ticker
time.Sleep(10 * time.Second)
ticker.Start() //检测 start
time.Sleep(10 * time.Second)
ticker.Start() //检测 start已启动ticker
time.Sleep(10 * time.Second)
ticker.Stop() //检测 stop
time.Sleep(10 * time.Second)
ticker.Start() //检测 重启开启ticker
time.Sleep(10 * time.Second)
ticker.Stop() //检测 重新关闭ticker
time.Sleep(10 * time.Second)
select {
}
}
代码效果
2020-07-18 22:37:38 关闭定时器...
2020-07-18 22:37:38 定时已经关闭
2020-07-18 22:37:48 启动定时器...
2020-07-18 22:37:48 定时启动
2020-07-18 22:37:49 定时器执行
2020-07-18 22:37:50 定时器执行
2020-07-18 22:37:51 定时器执行
2020-07-18 22:37:52 定时器执行
2020-07-18 22:37:53 定时器执行
2020-07-18 22:37:54 定时器执行
2020-07-18 22:37:55 定时器执行
2020-07-18 22:37:56 定时器执行
2020-07-18 22:37:57 定时器执行
2020-07-18 22:37:58 启动定时器...
2020-07-18 22:37:58 定时已经开启
2020-07-18 22:37:58 定时器执行
2020-07-18 22:37:59 定时器执行
2020-07-18 22:38:00 定时器执行
2020-07-18 22:38:01 定时器执行
2020-07-18 22:38:02 定时器执行
2020-07-18 22:38:03 定时器执行
2020-07-18 22:38:04 定时器执行
2020-07-18 22:38:05 定时器执行
2020-07-18 22:38:06 定时器执行
2020-07-18 22:38:07 定时器执行
2020-07-18 22:38:08 定时器执行
2020-07-18 22:38:08 关闭定时器...
2020-07-18 22:37:48 定时关闭 //defer导致 定时启动与定时关闭时间一致
2020-07-18 22:38:18 启动定时器...
2020-07-18 22:38:18 定时启动
2020-07-18 22:38:19 定时器执行
2020-07-18 22:38:20 定时器执行
2020-07-18 22:38:21 定时器执行
2020-07-18 22:38:22 定时器执行
2020-07-18 22:38:23 定时器执行
2020-07-18 22:38:24 定时器执行
2020-07-18 22:38:25 定时器执行
2020-07-18 22:38:26 定时器执行
2020-07-18 22:38:27 定时器执行
2020-07-18 22:38:28 关闭定时器...
2020-07-18 22:38:18 定时关闭 //defer导致 定时启动与定时关闭时间一致
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [select (no cases)]:// 说明go携程全部回收
main.main()
/Users/zyj/go/src/zyj.com/ticker/main.go:104 +0x15f
参考
[1]: https://my.oschina.net/renhc/blog/3027376/print
[2]: http://xiaorui.cc/archives/6109