GO的定时器Timer 和定时任务cron

GO的定时器Timer 和定时任务cron

上次我们说到了GO 中 swaggo 的应用,咱们来回顾一下

  • swaggo 是什么
  • swagger 是什么
  • 如何使用 swaggo
  • 如何测试 swaggo

要是对GO 中 swaggo 的应用还有点兴趣的话,可以查看文章 工作中后端是如何将API提供出去的?swaggo很不错

之后我们可以来一次 swaggo 的原理分享,细细的了解一下swaggo是如何生成swagger 文档的

今天咱们来看看 GO 里面的 定时器 Timer 和 定时任务 cron

咱们今天还是来看看 定时器 timer 和 定时任务 cron 如何使用,关于他们的原理,咱们后续文章会详细分享

Timer 是什么?

是 GO 中提供一个 定时器包,主要是用 time.Timer

timer 实际上是一种单一事件的定时器

也就是说,经过指定的时间后触发一个事件,这个事件通过其本身提供的 通道 进行通知 , 因为Timer只执行一次就结束,所以叫他单一事件

TimerTicker最重要的区别之一 就是这里了

大致流程是这个样子的:

Go 运行时会启动一个单独的 协程

该协程 执行了一个 timerproc 的函数,维护了一个 最小堆

该协程会定期被唤醒并读取堆顶的 timer 对象,执行该 timer 对象对应的函数(就是在 timer.C 中发送一条数据,用于触发定时器)

执行完毕后就会从最小堆中移除该 timer 对象

咱们创建的 time.Timer ,实际上就是在这个最小堆中添加一个 timer 对象实例,那么我们需要停止定时器,也就是使用 timer.Stop的时候,就是从这个堆里面删除对应的 timer 对象

本文先不细细说明实际原理,咱们先会简单应用它,后续会详细分享

万事开头难,然后中间难,最后结尾难

GO的定时器Timer 和定时任务cron_第1张图片

Timer 如何使用?

咱们简单看看 Timer 对应的数据结构

位置在: src/time/sleep.go:Timer

Timer代表一次定时,时间到来后只发生一个事件
只发生一次,这里尤为重要

Timer对外仅暴露一个通道,指定的时间到了,就会往该通道中写入系统时间,时间到了就触发一次事件,只会触发一次,因为时间只会到一次

type Timer struct { 
    C <-chan Time
    r runtimeTimer
}

咱们分别从如下几个场景使用一下 Timer

  • 基本使用
  • Time 延时使用
  • 停止定时器
  • 重置定时器

基本使用

咱们设置一个 1s 中的定时器,这个定时器只会触发一次

创建一个定时器:

func New*Timer*(d Duration) Timer

指定一个时间即可创建一个TimerTimer一经创建便开始计时,不需要额外的启动命令

func main() {
    // 创建一个 Timer
   myT := time.NewTimer(1 * time.Second)
    // 从通道中读取数据,若读取得到,说明时间到了
   <- myT.C
   fmt.Println(" 1 s 时间到")

   for {}
}

Time 延时使用

设置一个 1 秒的定时,再延时 2 秒

func main() {
    // 创建一个 Timer
   myT := time.NewTimer(1 * time.Second)
   <- myT.C
   fmt.Println(" 1 s 时间到 ",time.Now().Unix())
   
   // 延时 2 秒
   <-time.After(2 * time.Second)
   fmt.Println(" 2 s 时间到 ",time.Now().Unix())
   
   for {}
}

运行代码执行效果如下:

 1 s 时间到  1624757781
 2 s 时间到  1624757783

GO 还提供了一个函数 AfterFunc

func AfterFunc(d Duration, f func()) *Timer

也是可以做到延迟的效果,更好的是,延迟了之后,能够执行我们填入的函数

停止定时器

Timer 创建后可以随时停止,咱们可以使用time.Stop()停止定时器:

func (t *Timer) Stop() bool

Stop()函数返回值是 bool,要么是 true , 要么是 false , 代表的含义是 定时器是否超时

  • true

定时器超时前停止,后续不会再有事件发送了

  • false

定时器是在超时后,停止的

写一个DEMO , 设置 1 s 的定时器

若在到了1 s ,则进行打印,说明已经超时

若没有到 1 s ,通道就已经关闭了,则未超时

func testChannelTimeout(conn chan int) bool {
   // 设置 1 秒的定时器,若在到了1 s ,则进行打印,说明已经超时
   timer := time.NewTimer(1 * time.Second)

   select {
   case <-conn:
       if (timer.Stop()){
           fmt.Println("timer.Stop()")
       }
      return true
   case <-timer.C: // timer 通道超时
      fmt.Println("timer Channel timeout!")
      return false
   }
}

func main() {

   ch := make(chan int, 1)
    // 若打开如下语句,则可以正常关闭定时器
    // 若注释如下语句,则关闭定时器超时
   //ch <- 1
   go testChannelTimeout(ch)

   for {}
}

上述代码中,是否关闭定时器超时,跟另外一个辅助通道息息相关

若打开如下语句,则可以正常关闭定时器

若注释如下语句,则关闭定时器超时

ch <- 1

重置定时器

开局设置一个鱼的记忆,7秒的定时器

立刻将定时器重置成 1 秒的定时器

func main() {
   // 创建一个 Timer 鱼的记忆
   fmt.Println(" 开始 ", time.Now().Unix())
   myT := time.NewTimer(7 * time.Second)
   // 重置定时器为 1 s
   myT.Reset(1 * time.Second)
   <-myT.C
   fmt.Println(" 1 s 时间到 ", time.Now().Unix())

   for {}
}

运行上述代码后,效果如下:

 开始  1624759572
 1 s 时间到  1624759573

上述Timer 都是触发一次,生效一次,这样并不能满足所有场景,例如周期性定时执行的场景就不满足了

咱们可以使用 GO 里面的 Ticker

Ticker 是什么?

Ticker也是定时器,不过他是一个周期性的定时器,

也就是说,他用于周期性的触发一个事件,通过Ticker本身提供的管道将事件传递出去的

Ticker对外仅暴露一个通道,指定的时间到了,就往该通道中写入系统时间,也即一个事件。此处的时间到了,只的是周期性的时间到了

Ticker 如何使用?

位置在: src/time/tick.go:Timer

type Ticker structtype Timer struct { 一模一样

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

关于创建定时器 和 关闭定时器 和 上述的 Timer方法类似,咱们一起列举出来

创建Ticker 定时器(强调:这是一个周期性的定时器)

func NewTicker(d Duration) *Ticker

关闭Ticker 定时器

func (t *Ticker) Stop()

简单应用Ticker

设置 2 秒的 周期性定时器 Ticker

ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()

// 若通道为空,则阻塞
// 若通道有数据,则读取
// 若通道关闭,则退出
for range ticker.C {
   fmt.Println("ticker ticker ticker ...")
}

来一个通用版本的 DEMO

周期性的执行任务,我们可以灵活设置时间,和具体处理的任务

  • 封装Ticker的调用
// 定义函数类型
type Fn func() error

// 定时器中的成员
type MyTicker struct {
    MyTick *time.Ticker
    Runner Fn
}

func NewMyTick(interval int, f Fn) *MyTicker {
    return &MyTicker{
        MyTick: time.NewTicker(time.Duration(interval) * time.Second),
        Runner: f,
    }
}

// 启动定时器需要执行的任务
func (t *MyTicker) Start() {
    for {
        select {
        case <-t.MyTick.C:
            t.Runner()
        }
    }
}

func testPrint(){
    fmt.Println(" 滴答 1 次")
}

func main() {
    t := NewMyTick( 1 ,testPrint)
    t.Start()
}

执行上述代码,运行效果:

滴答 1 次
滴答 1 次
滴答 1 次
...

触发一次的Timer,周期性触发的Ticker,咱们都应用到了

GO的定时器Timer 和定时任务cron_第2张图片

cron 是什么?

看到 cron 小伙伴们应该不会陌生吧,用过 linux 的应该对 cron 还是有点想法的

linux里面咱们可以使用 crontab -e 来设置定时任务,GO 里面,我们也可以是使用 cron 包来设置定时任务

不过,linux里面 上述定时任务只支持 分钟以上级别

咱们的 GO 可以支持到 秒级别

cron 如何使用?

使用的包:"github.com/robfig/cron"

关于 cron 的基本语法和 在linux玩的时候类似,咱们来列举一下:

// 每隔1秒执行一次
*/1 * * * * ?

// 每隔1分钟执行一次
0 */1 * * * ?

// 每天0点执行一次
0 0 0 * * ?

// 每月1号凌晨1点执行一次
0 0 1 1 * ?

// 在1分、2分、3分执行一次
0 1,2,3 * * * ?

// 每天的0点、1点、2点执行一次
0 0 0,1,2 * * ?

解释一下上述的一些字符:

  • *

匹配该字段的所有值 , 例如 */1 * * * * ? 第 2 个 * 就是代表 每一分钟

  • /

表示增长间隔 ,例如 0 */1 * * * ? 表示,每一隔分钟执行一次

枚举值

例如秒, 可以写 1到59秒钟的任意数字, 1,3,5 * * * * ?,指的是每一分钟的 1 , 3 ,5秒 会执行任务

其中时、分、秒的可选范围是 1-59

日 可选范围是 1-31

月 可选范围是 1-12

年 可选范围是 1-12

星期 可选范围是 0-6 表示 周日 - 周六

  • -

表示一个范围, 例如 1-10/2 * * * * ? ,指每分钟的 1 -10,每隔 2 秒钟,执行任务

  • ?

用于 表示 或者 星期

来一个简单的例子

设置 每隔 2 秒钟 执行一次任务

func main() {
   i := 0
   c := cron.New()
   spec := "*/2 * * * * ?"
   err := c.AddFunc(spec, func() {
      i++
      fmt.Println("cron times : ", i)
   })
   if err != nil {
      fmt.Errorf("AddFunc error : %v",err)
      return 
   }
   c.Start()

   defer c.Stop()
   select {}
}

cron 用起来还是非常简单的,感兴趣的朋友,可以多多实践一下,关于他们的原理,咱么后续娓娓道来

GO的定时器Timer 和定时任务cron_第3张图片

总结

  • Timer 是什么
  • Timer 如何使用
  • Ticker 是什么
  • Ticker 如何使用
  • cron 是什么
  • cron 如何使用

欢迎点赞,关注,收藏

朋友们,你的支持和鼓励,是我坚持分享,提高质量的动力

GO的定时器Timer 和定时任务cron_第4张图片

好了,本次就到这里,下一次 GO 的日志如何玩

技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。

我是小魔童哪吒,欢迎点赞关注收藏,下次见~

你可能感兴趣的:(后端golang)