文档章节

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

o0无忧亦无怖
 o0无忧亦无怖
发布于 2018/05/23 00:21
字数 1167
阅读 705
收藏 6

golang中定时器

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

一般用法:

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为例:

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

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

设计我们的定时任务队列

我的需求

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

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

流程图

定义结构

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的时候添加一个默认的任务, 我是这么做的,添加了一个一小时执行一次的重复队列,防止队列退出。

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. 进行阻塞判断,获取我们要进行的操作
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功能

© 著作权归作者所有

共有 人打赏支持
o0无忧亦无怖
粉丝 41
博文 88
码字总数 70523
作品 1
海淀
程序员
私信 提问
加载中

评论(3)

o0无忧亦无怖
o0无忧亦无怖

引用来自“抢小孩糖吃”的评论

这个实现比较简单,建议参考java的实现重新开发下
哈哈 需求只要这么点 所以就只弄了这么点 至于同步和其他嘚东西 都在别嘚地方实现了 正考虑是否分布式+任务分发
抢小孩糖吃
抢小孩糖吃
这个实现比较简单,建议参考java的实现重新开发下
o0无忧亦无怖
o0无忧亦无怖
哪位大佬打赏,谢谢
JavaScript运行机制:event-loop

我们从javascript的单线程->任务队列->事件和回调函数->事件环,一步一步讲解javascript的执行机制。 一、JavaScript是单线程 JavaScript语言的特点是单线程,同一时间只能做一件事,JavaScr...

奋斗的小小鸟
2018/08/09
0
0
storm定时器timer源码分析-timer.clj

storm定时器与java.util.Timer定时器比较相似。java.util.Timer定时器实际上是个线程,定时调度所拥有的TimerTasks;storm定时器也有一个线程负责调度所拥有的"定时任务"。storm定时器的"定时...

Adel
2016/04/13
31
0
PHP的异步并行网络扩展Swoole已发布1.7.5版本

PHP的异步并行网络扩展Swoole今天发布了最新的1.7.5版本。 项目主页:http://www.swoole.com/ 文档页面:http://wiki.swoole.com/ 源代码:https://github.com/swoole/swoole-src 1.7.4版本:...

matyhtf
2014/09/10
2.8K
31
redis 在业务层面的应用之定时器

前几天出去面试,大家都喜欢聊redis,一个是底层数据结构的实现,一个是在业务层的使用,这里就结合一些简单的python代码,讲下怎样用redis 做应用层面的定时器。 首先,当大批量任务做超时管...

鼎铭
2018/07/30
0
0
Javascript 简要,你一定要看看

1.Virtual DOM Vue 和 React 都使用了 Virtual DOM,那么什么是 Virtual DOM? Virtual DOM 的诞生:改变真实DOM状态远比改变一个JavaScript对象的开销要大得多。 Virtual DOM 是一个映射真实...

互联网架构师
2018/09/20
0
0

没有更多内容

加载失败,请刷新页面

加载更多

聊聊flink的Async I/O

序 本文主要研究一下flink的Async I/O 实例 // This example implements the asynchronous request and callback with Futures that have the// interface of Java 8's futures (which is t......

go4it
12分钟前
0
0
一文详解微服务架构的数据设计

微服务是一个软件架构模式,对微服务的讨论大多集中在容器或其他技术是否能很好的实施微服务这些方面。 本文将从以下几个角度来和大家分享在微服务架构下进行数据设计需要关注的地方,旨在帮...

java菜分享
22分钟前
1
0
Java并发编程之美读书笔记-并发编程基础1

线程 进程是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,比如idea运行代码时的jvm是一个进程,但是CPU资源比较特殊,它是被分配到线程的,线程是进程的一个执行路...

hensemlee
35分钟前
1
0
【剑指offer纪念版】--10 进制1的个数

10. 题目 题目:请实现一个函数,输入一个整数,输出该数二进制表示中1的个数。例如把9表示成二进制是1001,有2位是1。因此如果输入9,该函数输出2。 解题思路   把一个整数减去1,再和原整...

细节探索者
50分钟前
1
0
本科毕业平均年薪 30 万!经济寒冬挡不住 AI 人才的火热!

互联网行业遭遇寒冬,企业纷纷裁员缩招,而 BAT 和硅谷明星公司对 AI 人才的投入却并不见放缓。为争夺相关人才,给应届毕业生开出的平均年薪高达 30 万。 而 TensorFlow 作为当下最流行的深度...

AI女神
55分钟前
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部