写点什么

两总定时任务调度器的调度实现对比

用户头像
xyu
关注
发布于: 1 小时前

前言

基于 golang 做项目开发的实践中,经常碰到需要任务的定时执行,所以不可避免地需要使用到定时任务管理器。现将常用的两款定时任务管理器拉出来,分析分析其实现机制,并总结如何在不同的场景下选择合适的定时任务管理器。

这两种定时任务管理器分别为:

alex023/clock

直接上任务调度核心代码:(PS: 以下的 Job 都指单个定时任务)

func (jl *Clock) schedule() {    var (        timeout time.Duration        job     *jobItem        timer   = newSafeTimer(_UNTOUCHED)    )    defer timer.Stop()Pause:    <-jl.resumeChan    for {        job, _ = jl.jobQueue.Min().(*jobItem) //ignore ok-assert        timeout = job.actionTime.Sub(time.Now())        timer.SafeReset(timeout)        select {        case <-timer.C:            timer.SCR()              atomic.AddUint64(&jl.count, 1)             job.action(true)             if job.actionMax == 0 || job.actionMax > job.actionCount {                jl.jobQueue.Delete(job)                job.actionTime = job.actionTime.Add(job.intervalTime)                jl.jobQueue.Insert(job)            } else {                jl.removeJob(job)            }        case <-jl.pauseChan:            goto Pause        case <-jl.exitChan:            goto Exit        }    }Exit:}
复制代码

分析其源码实现,可知其特性:

  1. 每次添加或删除 Job 实例,都需要停止调度器的执行,并重新组织 Jobs;

  2. 使用红黑数来组织 Job 实例,查找、插入、删除的时间复杂度为 O(lgn);

  3. 允许添加及删除 Job 实例;

robfig/cron

直接上任务调度核心代码:

// Run the scheduler. this is private just due to the need to synchronize// access to the 'running' state variable.func (c *Cron) run() {    // Figure out the next activation times for each entry.    now := c.now()    for _, entry := range c.entries {        entry.Next = entry.Schedule.Next(now)    }     for {        // Determine the next entry to run.        sort.Sort(byTime(c.entries))         var timer *time.Timer        if len(c.entries) == 0 || c.entries[0].Next.IsZero() {  // 如果没有要执行的任务或者第一个任务的待执行时间为空,则睡眠            // If there are no entries yet, just sleep - it still handles new entries            // and stop requests.            timer = time.NewTimer(100000 * time.Hour)        } else {            timer = time.NewTimer(c.entries[0].Next.Sub(now))   // 否则新建一个距离现在到下一个要触发执行的Timer        }         for {            select {            case now = <-timer.C:    // 触发时间到,执行任务                now = now.In(c.location)                // Run every entry whose next time was less than now                for _, e := range c.entries {                    if e.Next.After(now) || e.Next.IsZero() {                        break                    }                    go c.runWithRecovery(e.Job)                    e.Prev = e.Next                    e.Next = e.Schedule.Next(now)                }             case newEntry := <-c.add:    // 添加任务                timer.Stop()                now = c.now()                newEntry.Next = newEntry.Schedule.Next(now)                c.entries = append(c.entries, newEntry)             case <-c.snapshot:   // 调用c.Entries()返回一个现有任务列表的snapshot                c.snapshot <- c.entrySnapshot()                continue             case <-c.stop:   // 任务结束,退出                timer.Stop()                return            }             break        }    }}
复制代码

分析其源码实现,可知其特性:

  1. 只允许添加,不允许删除 Job;

  2. 使用普通的链表来组织 Job 实例,使用 sort.Sort()对链表进行排序,其理想情况下的时间复杂度为 O(nlgn); 故其读 Job 的时间复杂度为 O(1), 写 Job 的时间复杂度为 O(nlgn)。

对比与总结

  1. 从时间复杂度角度出发:clock 对 Job 的读写时间复杂度都为 O(lgn); cron 对 Job 的读写时间复杂度分别为 O(1)及 O(nlgn); 综合来看,clock 对 Job 操作的时间复杂度为 O(lgn), cron 对 Job 操作的时间复杂度为 O(nlgn); 故当 n(定时任务数)较大时,clock 对定时任务的调度具有性能优势;

  2. 从对 Job 操作的角度出发:clock 允许添加及删除 Job, cron 只允许添加 Job; 故当需要定时任务删除操作时,只能选 clock 方案;

  3. 从 API 使用角度出发:clock 方案为实现可随时删除 Job 的功能,需要额外维护一个 map 来存储具体的 Job 信息;其 API 使用起来较 cron 繁琐;相比之下 cron 方案的 API 更加简洁,相关定时配置也更加灵活;

  4. 从 Job 的性质出发:cron 更适合执行周期性执行的定时任务,clock 更适合执行非周期性执行的定时任务。

发布于: 1 小时前阅读数: 5
用户头像

xyu

关注

还未添加个人签名 2018.03.06 加入

还未添加个人简介

评论

发布
暂无评论
两个定时任务调度器的调度实现对比