目录

golang 写循环执行的定时任务,常见的有以下三种实现方式:

这三种定时器的实现原理

优劣性对比,使用建议


golang 写循环执行的定时任务,常见的有以下三种实现方式:

1、time.Sleep方法:

for {

   time.Sleep(time.Second)

   fmt.Println("我在定时执行任务")

}

2、time.Tick函数:

t1:=time.Tick(3*time.Second)

for {

   select {

   case <-t1:

      fmt.Println("t1定时器")

   }

}

3、其中Tick定时任务,也可以先使用time.Ticker函数获取Ticker结构体,然后进行阻塞监听信息,这种方式可以手动选择停止定时任务,在停止任务时,减少对内存的浪费。

t:=time.NewTicker(time.Second)

for {

   select {

   case <-t.C:

      fmt.Println("t1定时器")

      t.Stop()

   }

}

其中第二种和第三种可以归为同一类

这三种定时器的实现原理

一般来说,你在使用执行定时任务的时候,一般旁人会劝你不要使用time.Sleep完成定时任务,但是为什么不能使用Sleep函数完成定时任务呢,它和Tick函数比,有什么劣势呢?这就需要我们去探讨阅读一下源码,分析一下它们之间的优劣性。

首先,我们研究一下Tick函数,func Tick(d Duration) <-chan Time

调用Tick函数会返回一个时间类型的channel,如果对channel稍微有些了解的话,我们首先会想到,既然是返回一个channel,在调用Tick方法的过程中,必然创建了goroutine,该Goroutine负责发送数据,唤醒被阻塞的定时任务。我在阅读源码之后,确实发现函数中go出去了一个协程,处理定时任务。

按照当前的理解,使用一个tick,需要go出去一个协程,效率和对内存空间的占用肯定不能比sleep函数强。我们需要继续阅读源码才拿获取到真理。

简单的调用过程我就不陈述了,我在这介绍一下核心结构体和方法(删除了部分判断代码,解释我写在表格中):

func (tb *timersBucket) addtimerLocked(t *timer) {

   t.i = len(tb.t)  //计算timersBucket中,当前定时任务的长度

   tb.t = append(tb.t, t)// 将当前定时任务加入timersBucket

   siftupTimer(tb.t, t.i)  //维护一个timer结构体的最小堆(四叉树),排序关键字为执行时间,即该定时任务下一次执行的时间

   if !tb.created {

      tb.created = true

      go timerproc(tb)// 如果还没有创建过管理定时任务的协程,则创建一个,执行通知管理timer的协程,最核心代码

   }

}

timersBucket,顾名思义,时间任务桶,是外界不可见的全局变量。每当有新的timer定时器任务时,会将timer加入到timersBucket中的timer切片。timerBucket结构体如下:

type timersBucket struct {

   lock         mutex //添加新定时任务时需要加锁(冲突点在于维护堆)

   t            []*timer //timer切片,构造方式为四叉树最小堆

}

func timerproc(tb *timersBucket) 详细介绍

可以称之为定时任务处理器,所有的定时任务都会加入timersBucket,然后在该函数中等待被处理。等待被处理的timer,根据when字段(任务执行的时间,int类型,纳秒级别)构成一个最小堆,每次处理完成堆顶的某个timer时,会给它的when字段加上定时任务循环间隔时间(即Tick(d Duration) 中的d参数),然后重新维护堆,保证when最小的timer在堆顶。当堆中没有可以处理的timer(有timer,但是还不到执行时间),需要计算当前时间和堆顶中timer的任务执行时间差值delta,定时任务处理器沉睡delta段时间,等待被调度器唤醒。核心代码如下(注释写在每行代码的后面,删除一些判断代码以及不利于阅读的非核心代码):

func timerproc(tb *timersBucket) {

   for {

      lock(&tb.lock) //加锁

      now := nanotime()  //当前时间的纳秒值

      delta := int64(-1 //最近要执行的timer和当前时间的差值

      for {

         if len(tb.t) == 0 {

            delta = -1

            break

         }//当前无可执行timer,直接跳出该循环

         t := tb.t[0]

         delta = t.when - now //取when组小的的timer,计算于当前时间的差值

         if delta > 0 {

            break

         }// delta大于0,说明还未到发送channel时间,需要跳出循环去睡眠delta时间

         if t.period > 0 {

            // leave in heap but adjust next time to fire

            t.when += t.period * (1 + -delta/t.period)// 计算该timer下次执行任务的时间

            siftdownTimer(tb.t, 0) //调整堆

         } else {

            // remove from heap,如果没有设定下次执行时间,则将该timer从堆中移除(time.after和time.sleep函数即是只执行一次定时任务)

            last := len(tb.t) - 1

            if last > 0 {

               tb.t[0] = tb.t[last]

               tb.t[0].i = 0

            }

            tb.t[last] = nil

            tb.t = tb.t[:last]

            if last > 0 {

               siftdownTimer(tb.t, 0)

            }

            t.i = -1 // mark as removed

         }

         f := t.f

         arg := t.arg

         seq := t.seq

         unlock(&tb.lock)//解锁

         f(arg, seq) //在channel中发送time结构体,唤醒阻塞的协程

         lock(&tb.lock)

      }

      if delta < 0  {

         // No timers left - put goroutine to sleep.

         goparkunlock(&tb.lock, "timer goroutine (idle)", traceEvGoBlock, 1)

         continue

      }// delta小于0说明当前无定时任务,直接进行阻塞进行睡眠

      tb.sleeping = true

      tb.sleepUntil = now + delta

      unlock(&tb.lock)

      notetsleepg(&tb.waitnote, delta)  //睡眠delta时间,唤醒之后就可以执行在堆顶的定时任务了

   }

}

至此,time.Tick函数涉及到的主要功能就讲解结束了,总结一下就是启动定时任务时,会创建一个唯一协程,处理timer,所有的timer都在该协程中处理。

然后,我们再阅读一下sleep的源码实现,核心源码如下:

//go:linkname timeSleep time.Sleep

func timeSleep(ns int64) {

   *t = timer{} //创建一个定时任务

   t.when = nanotime() + ns //计算定时任务的执行时间点

   t.f = goroutineReady //执行方法

   tb.addtimerLocked(t)  //加入timer堆,并在timer定时任务执行协程中等待被执行

   goparkunlock(&tb.lock, "sleep", traceEvGoSleep, 2) //睡眠,等待定时任务协程通知唤醒

}

读了sleep的核心代码之后,是不是突然发现和Tick函数的内容很类似,都创建了timer,并加入了定时任务处理协程。神奇之处就在于,实际上这两个函数产生的timer都放入了同一个timer堆,都在定时任务处理协程中等待被处理。

优劣性对比,使用建议

现在我们知道了,Tick,Sleep,包括time.After函数,都使用的timer结构体,都会被放在同一个协程中统一处理,这样看起来使用Tick,Sleep并没有什么区别。

实际上是有区别的,Sleep是使用睡眠完成定时任务,需要被调度唤醒。Tick函数是使用channel阻塞当前协程,完成定时任务的执行。当前并不清楚golang 阻塞和睡眠对资源的消耗会有什么区别,这方面不能给出建议。

但是使用channel阻塞协程完成定时任务比较灵活,可以结合select设置超时时间以及默认执行方法,而且可以设置timer的主动关闭,以及不需要每次都生成一个timer(这方面节省系统内存,垃圾收回也需要时间)。

所以,建议使用time.Tick完成定时任务。

golang 定时任务方面time.Sleep和time.Tick的优劣对比相关推荐

  1. golang定时任务的使用

    golang定时任务的使用 由于 golang 标准库内没有比较好的定时任务包,所以这里将使用一个第三方的 cron 包,由于此包存在一些不适用的地方,所以对此做了一些简单封装. cron简介 rob ...

  2. Golang 定时任务 github/robfig/cron/v3 使用与源码解析

    Cron 源码阅读 robfig/cron/v3 是一个 Golang 的定时任务库,支持 cron 表达式.Cron 的源码真实教科书级别的存在(可能是我菜 -),真的把低耦合高内聚体现地淋漓尽致, ...

  3. 从Golang调度器的作者视角探究其设计之道!

    导语 | Golang核心开发人员.goroutine调度的设计者Dmitry Vyukov,在2019年的一个talk里深入浅出地阐述了goroutine调度的设计思想以及一些优化的细节.本文是笔者 ...

  4. 实现了一个golang周期任务调度包

    drumstick Implement crond by Golang https://github.com/openex27/drumstick 鼓槌(鸡腿),golang定时任务包 功能特性: 1 ...

  5. Let‘s Go Rust 系列之定时器 Ticker Timer

    前言 在实际项目开发中,经常会有定时任务的功能开发需求,定时任务主要分为两种, 1,在固定的时刻执行某个任务,也就是 Timer 2,基于固定的时间间隔,周期的执行某个任务,也就是Ticker ​ 很 ...

  6. Game as a Service —— 开源云游戏搭载WebRTC

    软件即服务,基础架构即服务,平台即服务,通信平台即服务,视频会议即服务,那么,游戏即服务(Game as a Service)如何呢?已经有不少科技公司试水云游戏,最著名的要数Google的Stadi ...

  7. Netty系列之Netty线程模型

    关注点在于:如何灵活的动态绑定IO事件处理,又能进行串行化处理减少锁的使用 摘自:http://www.infoq.com/cn/articles/netty-threading-model 1. 背 ...

  8. zabbix二次开发之从mysql取值在运维平台js图表展现

    前沿: 集群控制平台已经要慢慢的灰度上线了,出问题的时候,才找点bug,时间有点空闲.正好看了下zabbix的数据库,产生了自己想做一套能更好的展现zabbix的页面. 更多内容请到我的个人的博客站点 ...

  9. 那些在一个公司死磕5-10年的人,最后都怎么样了...

    很多人都不了解,JVM中的对象是可以进行栈上分配和TLAB(线程本地分配)的,看我用动画给你演示这个过程! 不光深入,而且生动,用最鲜活的展现手段让你进行轻松的提升. 细想想现在互联网大厂中,面对JV ...

最新文章

  1. 行业洞察驱动安全防御严峻安全挑战迎刃而解
  2. python中如何将字符串连接在一起,多倍的字符串如何输出
  3. 记录 之 在华为NPU上变更镜像
  4. 前后落差大用什么词语_形容落差很大的成语_四字词语 - 成梦词典
  5. Java入门系列-20-异常
  6. spring cloud bus_Spring Cloud学习笔记--消息总线(Bus)
  7. 新rust怎么拆除围墙_“问题围挡”拆除 街道变漂亮了
  8. 转 8天入门wpf—— 第六天 细说控件
  9. OLAP-druid-大数据Week13-DAY2-druid
  10. 建模步骤_带你十个步骤学建模(二)
  11. love~LBJ,奥布莱恩神杯3
  12. [UOJ#132][BZOJ4200][luogu_P2304][NOI2015]小园丁与老司机
  13. xcode 免cleanup build
  14. macOS Big Sur 11.2.2 (20D80) 虚拟机 ISO 镜像
  15. 为什么这么多人怼我?或许是这个原因
  16. HTML播放华为云视频流,华为云点播服务视频管理功能强大,上云就是省心
  17. 以窗口形式进行数字图像处理时,在图像边界处对超出图像边界的窗口内容的映射(对称)处理
  18. to be top。。。
  19. 用网盘(dropbox,kuaipan,everbox)保管的git repository
  20. java entry getvalue_Java Entry.getValue方法代碼示例

热门文章

  1. Android实现了豆瓣FM的首页效果
  2. java文件批量重命名文件,文件批量工具(File Attribute Changer)
  3. 如何查看自己steam库里游戏是哪个区的
  4. 新思创OA办公自动化解决方案
  5. 检修计算机硬件故障的流程,计算机硬件日常管理维护及故障检修
  6. 针对 Telnet 协议的威胁观察
  7. ET表格在线转换成Excel
  8. 转置-置换-向量空间R
  9. 压力眼这一Part,久等啦!
  10. 汽车钥匙芯片工作原理 浅谈汽车钥匙芯片作用及分类