脚本之家,脚本语言编程技术及教程分享平台!
分类导航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服务器之家 - 脚本之家 - Golang - 用golang实现一个定时器任务队列实例

用golang实现一个定时器任务队列实例

2020-05-15 10:21o0无忧亦无怖 Golang

golang中提供了2种定时器timer和ticker,分别是一次性定时器和重复任务定时器。这篇文章主要介绍了用golang实现一个定时器任务队列实例,非常具有实用价值,需要的朋友可以参考下

 很有幸得到公司信任,采用新的语言进行一些底层服务的开发,在实现功能的同时,也获得了一些感悟,因此在这记录一下,方便自己查看也可以共享给大家。

golang定时器

golang中提供了2种定时器timer和ticker(如果JS很熟悉的话应该会很了解),分别是一次性定时器和重复任务定时器。

一般用法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
func main() {
 input := make(chan interface{})
 //producer - produce the messages
 go func() {
  for i := 0; i < 5; i++ {
   input <- i
  }
  input <- "hello, world"
 }()
 
 t1 := time.NewTimer(time.Second * 5)
 t2 := time.NewTimer(time.Second * 10)
 
 for {
  select {
  //consumer - consume the messages
  case msg := <-input:
   fmt.Println(msg)
 
  case <-t1.C:
   println("5s timer")
   t1.Reset(time.Second * 5)
 
  case <-t2.C:
   println("10s timer")
   t2.Reset(time.Second * 10)
  }
 }
}

源码观察

这个C是啥,我们去源码看看,以timer为例:

?
1
2
3
4
type Timer struct {
 C <-chan Time
 r runtimeTimer
}

原来是一个channel,其实有GO基础的都知道,GO的运算符当出现的->或者<-的时候,必然是有一端是指channel。按照上面的例子来看,就是阻塞在一个for循环内,等待到了定时器的C从channel出来,当获取到值的时候,进行想要的操作。

设计我们的定时任务队列

我的需求

当时我的需求是这样,我需要接收到客户端的请求并产生一个定时任务,会在固定时间执行,可能是一次,也可能是多次,也可能到指定时间自动停止,可能当任务终止的时候,我还要能停止掉。

具体我画了个流程图,差不多如下,画图水平有限,请见谅。

用golang实现一个定时器任务队列实例

定义结构

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type OnceCron struct {
 tasks []*Task   //任务的列队
 add chan *Task  //当遭遇到新任务的时候
 remove chan string  //当遭遇到删除任务的时候
 stop chan struct{}  //当遇到停止信号的时候
 Logger *log.Logger  //日志
}
type Job interface {
 Run()     //执行接口
}
type Task struct {
  Job  Job   //要执行的任务
 Uuid string   //任务标识,删除时用
 RunTime int64   //执行时间
 Spacing int64   //间隔时间
 EndTime int64   //结束时间
 Number int    //总共要次数
}

队列实现

首先,我们要获得一个队列任务

func NewCron() *OnceCron 常规操作,为了节省篇幅,我就不写出来,具体可以看源码,贴在了底部。

然后,开始定时器队列的运行,一般,都会命名为Start。那么就有一个问题,我们刚开始启动程序的时候,这个时候是没有任务队列,那岂不是for{ select{}}在等待个毛毛球?所以,我们需要在Start的时候添加一个默认的任务, 我是这么做的,添加了一个一小时执行一次的重复队列,防止队列退出。

?
1
2
3
4
5
6
7
8
func (one *OnceCron) Start() {
 //初始化的時候加入一個一年的長定時器,間隔1小時執行一次
 task := getTaskWithFuncSpacing(3600, time.Now().Add(time.Hour*24*365).Unix() , func() {
 log.Println("It's a Hour timer!")
 }) //为了代码格式markdown 里面有个括号我改成全角了
 one.tasks = append(one.tasks, task)
 go one.run() //协成执行 防止主进程被阻塞
}

执行部分应该是重点的,我的理解是,分成三部:

  1. 首先获得一个最先执行的任务
  2. 然后产生一个定时器,用于执行任务
  3. 进行阻塞判断,获取我们要进行的操作
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
func (one *OnceCron) run() {
 
 for {
    //第一步 获取任务
 now := time.Now() //获取到当前时间
 task, key := one.GetTask() //获取最近的一个任务的执行时间
 i64 := task.RunTime - now.Unix() //任务执行和当前时间的差
 
 var d time.Duration
 if i64 < 0 { //如果任务时间已过期,将执行时间改成现在并且利马执行
 one.tasks[key].RunTime = now.Unix()
 one.doAndReset(key)
      continue
 } else { //否则,获取距离执行开始的间隔时间
 d = time.Unix(task.RunTime, 0).Sub(now)
 }
    //第二步 产生定时器
 timer := time.NewTimer(d)
 
 //第三步 捕获定时器或者其他事件
 for {
 select {
      //当定时器到了执行时间时,执行当前任务并关闭定时器
 case <-timer.C:
 one.doAndReset(key)
 if task != nil {
  go task.Job.Run()
  timer.Stop()
 }
 
 //当外部添加了任务时,关闭当前定时器
 case <-one.add:
 timer.Stop()
 //当外部要删除一个任务时,删除ID为uuidstr的任务
 case uuidstr := <-one.remove:
 one.removeTask(uuidstr)
 timer.Stop()
 //当遇到要关闭整个定时器任务时
 case <-one.stop:
 timer.Stop()
 return
 }
 
 break
 }
 }
}

后记

这个文章纯粹为笔记分析类的文章,旨在分析我碰到一个需求是如何通过分析过程来产生我们需要的代码的。

源码地址:timing 一个任务队列

应用地址:一个应用于谷歌消息推送的转发中间件

参考源码:GOLANG实现crontab功能

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

原文链接:https://my.oschina.net/lwl1989/blog/1817024

延伸 · 阅读

精彩推荐
  • Golanggolang的httpserver优雅重启方法详解

    golang的httpserver优雅重启方法详解

    这篇文章主要给大家介绍了关于golang的httpserver优雅重启的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,...

    helight2992020-05-14
  • Golanggolang 通过ssh代理连接mysql的操作

    golang 通过ssh代理连接mysql的操作

    这篇文章主要介绍了golang 通过ssh代理连接mysql的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    a165861639710342021-03-08
  • Golanggo日志系统logrus显示文件和行号的操作

    go日志系统logrus显示文件和行号的操作

    这篇文章主要介绍了go日志系统logrus显示文件和行号的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    SmallQinYan12302021-02-02
  • Golanggolang json.Marshal 特殊html字符被转义的解决方法

    golang json.Marshal 特殊html字符被转义的解决方法

    今天小编就为大家分享一篇golang json.Marshal 特殊html字符被转义的解决方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧 ...

    李浩的life12792020-05-27
  • GolangGolang中Bit数组的实现方式

    Golang中Bit数组的实现方式

    这篇文章主要介绍了Golang中Bit数组的实现方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    天易独尊11682021-06-09
  • Golanggolang如何使用struct的tag属性的详细介绍

    golang如何使用struct的tag属性的详细介绍

    这篇文章主要介绍了golang如何使用struct的tag属性的详细介绍,从例子说起,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看...

    Go语言中文网11352020-05-21
  • Golanggo语言制作端口扫描器

    go语言制作端口扫描器

    本文给大家分享的是使用go语言编写的TCP端口扫描器,可以选择IP范围,扫描的端口,以及多线程,有需要的小伙伴可以参考下。 ...

    脚本之家3642020-04-25
  • GolangGolang通脉之数据类型详情

    Golang通脉之数据类型详情

    这篇文章主要介绍了Golang通脉之数据类型,在编程语言中标识符就是定义的具有某种意义的词,比如变量名、常量名、函数名等等,Go语言中标识符允许由...

    4272021-11-24