赞
踩
timer 简单来说就是1 个定时器,代表多少秒后执行,当创建1个timer,1秒钟过后,我们就能从timer.C 获取那个时刻的时间,因为系统在那个时刻将当前时间写入到timer.C 了,这时候我们就可以做自己的想做的事了。
- package main
-
- import (
- "fmt"
- "time"
- )
-
- func main() {
- timer:=time.NewTimer(1*time.Second)
- defer timer.Stop()
- msg:=<-timer.C
- fmt.Println("1秒后打印",msg)
- }
- //结果:1秒后打印 2021-11-02 22:43:42.2260892 +0800 CST m=+1.016042901
-
- type Timer struct {
- C <-chan Time //存储时间的管道
- r runtimeTimer //底层存储timer 的堆实现
- }
- type runtimeTimer struct {
- pp uintptr
- when int64 //什么时候触发timer
- period int64 //如果是周期性任务,执行周期性任务的时间间隔
- f func(interface{}, uintptr) // NOTE: must not be closure//到时候执行的回调函数
- arg interface{} //执行任务的参数
- seq uintptr//回调函数的参数,该参数仅在 netpoll 的应用场景下使用。
- nextwhen int64//如果是周期性任务,下次执行任务时间
- status uint32//timer 的状态
- }
下面只展示跟timer 有关的字段
- //Go\src\runtime\runtime2.go +604
- type p struct {
- .....
- //堆顶元素什么时候执行
- timer0When uint64
-
- //如果有timer 修改为更早执行时间了,将会将执行时间更新到当更早时间
- timerModifiedEarliest uint64
-
- //操作timer 的互斥锁
- timersLock mutex
-
- //该p 上的所有timer,必须加锁去操作这个字段,因为不同的p 操作这个字段会有竞争关系
- timers []*timer
-
- //p 堆上所有的timer
- numTimers uint32
-
- //被标记为删除的timer,要么是我们调用stop,要么是timer 自己触发后过期导致的删除
- deletedTimers uint32
- ....
- }

但是与我们常见的,使用二叉树来实现最小堆不同,Golang 这里采用了四叉堆 (4-heap) 来实现。这里 Golang 并没有直接给出解释。 这里直接贴一段 知乎网友对二叉堆和 N 叉堆的分析。
倍。
C 语言知名开源网络库 libev,其timer定时器实现可以在编译时选择采用四叉堆。在它的注释里提到四叉树相比来说缓存更加友好。 根据benchmark,在 50000 + 个 timer 的场景下,四叉树会有 5% 的性能优势。具体可见 libev/ev.c#L2227
- //创建一个将会在Duration 时间后将那一刻的时间发生到C 的timer
- func NewTimer(d Duration) *Timer {
- c := make(chan Time, 1) //创建1个channel
- t := &Timer{ //创建一个timer
- C: c,
- r: runtimeTimer{
- when: when(d), //什么时候执行
- f: sendTime, //到时候执行的回调函数
- arg: c,//执行参数
- },
- }
- startTimer(&t.r) //开始timer
- return t
- }
sendTime实现
- //c interface{} 就是NewTimer 赋值的参数,就是channel
- func sendTime(c interface{}, seq uintptr) {
- select {
- case c.(chan Time) <- Now(): //写不进去的话,C 已满,走default 分支
- default:
- }
- }
和NewTimer 一样 ,只是NewTimer 的语法糖,将创建的timer 的C返回了,这样就可以直接使用了
-
- func After(d Duration) <-chan Time {
- return NewTimer(d).C
- }
这个函数代表在多少秒后执行传入的回调函数f,并返回timer
-
- func AfterFunc(d Duration, f func()) *Timer {
- t := &Timer{
- r: runtimeTimer{
- when: when(d),//什么时候触发
- f: goFunc, //到时候执行的回调函数
- arg: f//参数为回调函数
- },
- }
- startTimer(&t.r)
- return t
- }
- //执行的回调函数
- func goFunc(arg interface{}, seq uintptr) {
- go arg.(func())()
- }

- func (t *Timer) Stop() bool {
- if t.r.f == nil {
- panic("time: Stop called on uninitialized Timer")
- }
- return stopTimer(&t.r) //调用系统的 stopTimer
- }
- package main
-
- import (
- "fmt"
- "time"
- )
-
- //timer 正常stop
- func Test1() {
- timer:=time.NewTimer(1*time.Second)
- fmt.Println(timer.Stop()) //true
- }
- //timer 已调用stop
- func Test2() {
- timer:=time.NewTimer(1*time.Second)
- timer.Stop()
- fmt.Println(timer.Stop()) //false
- }
- //timer 已被激活
- func Test3() {
- timer:=time.NewTimer(1*time.Second)
- <-timer.C
- fmt.Println(timer.Stop())//false
- }
- func main() {
- Test1()
- Test2()
- Test3()
-
- }
-

- if !t.Stop() {
- <-t.C
- }
- package main
-
- import (
- "time"
- )
-
- func main() {
- timer:=time.NewTimer(1*time.Second)
- for i:=0;i<10;i++{
- <-timer.C
- }
- }
- //结果
- //fatal error: all goroutines are asleep - deadlock!
-
- goroutine 1 [chan receive]:
- main.main()
- D:/code/leetcode/timer.go:10 +0x59
-

来看看并发的调用stop
- package main
-
- import (
- "fmt"
- "time"
- )
-
- func main() {
- timer:=time.NewTimer(1*time.Second)
- for i:=0;i<100;i++{
- go fmt.Println(timer.Stop())
- }
- }
- //虽然没有错误产生,但是也是不可取的,多次执行返回的状态却不一定是正确的.
- false
- false
- false
- true //顺序不固定,多次执行结果不一样
- false

- package main
-
- import (
- "fmt"
- "time"
- )
-
- func main() {
- var isComplete bool
- timer:=time.AfterFunc(1*time.Second, func() {
- time.Sleep(5*time.Second)
- fmt.Println("任务完成")
- isComplete=true //此例子不是并发操作
- })
- time.Sleep(2*time.Second) //让timer 过期激活
- fmt.Println(timer.Stop(),"isComplete=",isComplete) //true isComplete= false
- for {
- if isComplete{
- break
- }
-
- }
- fmt.Println("任务完成",isComplete)
- }
- //下面是执行结果
- //false isComplete= false
- //任务完成
- //任务完成 true
-

Ticker 形容时钟滴答滴答的声音,在go 中常用来做定时任务,任务到了执行任务。
Ticker 使用案例,常用来做定时任务或者顶层连接心跳,每秒定时做什么
- package main
-
- import (
- "fmt"
- "time"
- )
-
- func main() {
- t:=time.NewTicker(1*time.Second)
- defer t.Stop()
- for now:=range t.C{
- fmt.Println(now)
- }
- }
-
-
- type Ticker struct {
- C <-chan Time //chan 定时到了以后,go 系统会忘里面添加一个当前时间的数据
- r runtimeTimer
- }
- func NewTicker(d Duration) *Ticker {
- if d <= 0 {
- panic(errors.New("non-positive interval for NewTicker"))
- }
- //这里预留一个缓冲给timer 一样,但是满了以后没人接收后面会丢掉事件
- c := make(chan Time, 1)
- t := &Ticker{
- C: c,
- r: runtimeTimer{
- when: when(d),
- period: int64(d),
- f: sendTime, //和ticker 的函数一样
- arg: c,
- },
- }
- startTimer(&t.r)
- return t
- }

调用stopTimer停止ticker,停止不会关闭 channel。channel也不能被并发读
- func (t *Ticker) Stop() {
- stopTimer(&t.r)
- }
调用modTimer修改时间,接下来的激活将在新period后
- func (t *Ticker) Reset(d Duration) {
- if t.r.f == nil {
- panic("time: Reset called on uninitialized Ticker")
- }
- modTimer(&t.r, when(d), int64(d), t.r.f, t.r.arg, t.r.seq)
- }
- func Tick(d Duration) <-chan Time {
- if d <= 0 {
- return nil
- }
- return NewTicker(d).C
- }
- func timeSleep(ns int64) {
- if ns <= 0 {
- return
- }
-
- gp := getg()
- t := gp.timer
- if t == nil {
- t = new(timer)
- gp.timer = t
- }
- t.f = goroutineReady
- t.arg = gp
- t.nextwhen = nanotime() + ns
- if t.nextwhen < 0 { // check for overflow.
- t.nextwhen = maxWhen
- }
- gopark(resetForSleep, unsafe.Pointer(t), waitReasonSleep, traceEvGoSleep, 1)
- }
-
- func resetForSleep(gp *g, ut unsafe.Pointer) bool {
- t := (*timer)(ut)
- resettimer(t, t.nextwhen)
- return true
- }

注意:本文为go 在1.17.2 下面的源码解析,如果是1.15 或者1.16 可能有些不同
来看看下面这些函数,go/src/time 是找不到任何实现的,其实go 官方包
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。