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完成定时任务。

Go:定时执行任务time.sleep和time.tick的优劣的更多相关文章

  1. C#定时执行

    代码: using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; ...

  2. MVC 定时执行任务

    环境:.net4.5 需求:需要一个方法定时执行任务 解决: System.Threading.Timer 提供以指定的时间间隔执行方法的机制. 此类不能被继承,有10多种实例化方法,满足多种情况. ...

  3. 【转】linux 定时执行shell脚本

    在oracle 中可以利用dbms_job包定时执行pl/sql.sql过程,在像备份等需要在操作系统级定时任务只能采用crontab来完成 本文讲述crontab具体用法,以供备忘. 在oracle ...

  4. Linux下定时执行脚本(转自Decode360)

    文章来自:http://www.blogjava.net/decode360/archive/2009/09/18/287743.html Decode360's Blog  老师(业精于勤而荒于嬉 ...

  5. [转]oracle job有定时执行的功能,可以在指定的时间点或每天的某个时间点自行执行任务。

    oracle job有定时执行的功能,可以在指定的时间点或每天的某个时间点自行执行任务. 一.查询系统中的job,可以查询视图 --相关视图 select * from dba_jobs; selec ...

  6. linux定时执行任务crontab命令用法

    linux系统的定时任务是由 cron (crond) 这个系统服务来控制的.Linux 系统上面原本就有非常多的计划性工作,因此这个系统服务是默认启动的.另外, 由于使用者自己也可以设置计划任务,所 ...

  7. ORACLE 定时执行存储过程

    推荐用dbms_scheduler方式更好 (2012-11-19注) /* 查询: select job,broken,what,interval,t.* from user_jobs t; job ...

  8. oracle job有定时执行的功能,可以在指定的时间点或每天的某个时间点自行执行任务。

    来源于:http://www.cnblogs.com/wangfg/p/5110831.html 一.查询系统中的job,可以查询视图 --相关视图 select * from dba_jobs; s ...

  9. linux下定时执行任务方法【转】

     之前就转过一篇关于定时任务的文章,前俩天用,还的翻出来看!!!再转一次,备用,,需要的时候不用麻烦找! ----------------------------------------------- ...

随机推荐

  1. unity3D实现多点触碰

    实现多点触碰是利用input这个类里面的方法实现的. 从edit-project settings-input就可以看到input能够得到的轴. 想要读取轴向可以使用Input.GetAxis方法获取 ...

  2. R语言的学习笔记 (持续更新.....)

    1. DATE 处理 1.1 日期格式一个是as.Date(XXX) 和strptime(XXX),前者为Date格式,后者为POSIXlt格式 1.2 用法:as.Date(XXX,"%Y ...

  3. 计算机_网络_01_配置IE代理

    一.配置代理 1.打开代理设置 打开chrome浏览器设置->高级设置->系统->打开代理设置 2.打开局域网设置 Internet属性->连接->局域网设置 3.配置代 ...

  4. FFMPEG 最简滤镜filter使用实例(实现视频缩放,裁剪,水印等)

    FFMPEG官网给出了FFMPEG 滤镜使用的实例,它是将视频中的像素点替换成字符,然后从终端输出.我在该实例的基础上稍微的做了修改,使它能够保存滤镜处理过后的文件.在上代码之前先明白几个概念: Fi ...

  5. CodeForces - 1017 C. The Phone Number(数学)

    Mrs. Smith is trying to contact her husband, John Smith, but she forgot the secret phone number! The ...

  6. Gym 100801J Journey to the "The World's Start"(二分+单调队列)

    题意: 现在有1,2,3...N这N个站, 给定限定时间Limt,  N-1种票的价格, 分别对应一个最远距离,  叫你选择一种票, 满足可以在规定时间到达N站台,而且价格最低 思路: 如果买距离为L ...

  7. C/C++面试题总结(1)

    首先说一下,这些东西,有的是必须掌握的,有的是面试时你讲出来就是闪光点.自己把握.把握不好的都搞懂.实在不行背下来. 由于时间关系,总结的比较随意,有的就直接贴链接了,希望理解一下. 第一篇:基础(必 ...

  8. BZOJ2120:数颜色

    浅谈树状数组与线段树:https://www.cnblogs.com/AKMer/p/9946944.html 题目传送门:https://www.lydsy.com/JudgeOnline/prob ...

  9. 开发框架:AdminLTE

    ylbtech-开发框架:AdminLTE 1.返回顶部   2.返回顶部   3.返回顶部   4.返回顶部 1. 2. 5.返回顶部 1. https://adminlte.io 2.   6.返 ...

  10. 编译内核是出现:arch/arm/mm/tlb-v4wbi.S:64:error: too many positional arguments

    内核:Linux-3.4.2 编译内核出现arch/arm/mm/tlb-v4wbi.S:64:error: too many positional arguments 交叉工具链太老了,换新一点的. ...