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

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

image

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

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

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

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

image

今天咱们来看看 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 对象

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

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

image

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 都是触发一次,生效一次,这样并不能满足所有场景,例如周期性定时执行的场景就不满足了

image

咱们可以使用 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,咱们都应用到了

image

cron 是什么?

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

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

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

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

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 用起来还是非常简单的,感兴趣的朋友,可以多多实践一下,关于他们的原理,咱么后续娓娓道来

image

总结

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

欢迎点赞,关注,收藏

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

[图片上传失败...(image-3b61f6-1624805079524)]

好了,本次就到这里,下一次 GO 的定时器 timer 和定时任务cron

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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,509评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,806评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,875评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,441评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,488评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,365评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,190评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,062评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,500评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,706评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,834评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,559评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,167评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,779评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,912评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,958评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,779评论 2 354

推荐阅读更多精彩内容