乐趣区

关于后端: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 对象

本文先不细细阐明理论原理,咱们先会简略利用它,后续会具体分享

万事开头难,而后两头难,最初结尾难

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,咱们都利用到了

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 用起来还是非常简单的,感兴趣的敌人,能够多多实际一下,对于他们的原理,咱么后续娓娓道来

总结

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

欢送点赞,关注,珍藏

敌人们,你的反对和激励,是我保持分享,提高质量的能源

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

技术是凋谢的,咱们的心态,更应是凋谢的。拥抱变动,背阴而生,致力向前行。

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

退出移动版