Goland中time.Timer and time.Ticker
本文介绍 Timer
, Tick
, Sleep
的实现机制。版本是 GO 1.9 。
thinking:goland ticker + redis 实现纳秒级的定时器。redis作为注册【配置中心-开关/触发间隔】中心,ticker作为执行中心,脚本逻辑自定义
Ticker
每隔 duration
时间会把当前的时间点放入到 channel
中,应用可以从 channel
进行读取。应用需要周期性的时间间隔,可以使用此方法。
thinking:times数组【全局变量维护全部timer\ticker】死循环进行when值比较,满足间隔时间就会进行响应的注册事件chan写入时间,触发事件发生,同时更新when值,重新下沉此事件维持最小堆形态
使用 Ticker
有两种方式, NewTicker
可以获取 Ticker
实例, Stop
可以显示的停止 Tick
运行。 Stop
可以释放 timer
资源,但不会关闭 channel
,防止应用层报错。
stopTicker是关闭系统协程,并不关闭channel,不执行stop将会导致内存泄露,timer则不用,执行结束将自动从times中删除,即关闭系统协程
如果 Ticker
一直随应用运行,不会关闭,可以使用 time.Tick
直接获取 time channel
。这个没有 Ticker
实例,无法显示关闭。 timer
会一直运行。
Timer
定时器,和 Tick
类似,经过 duration
时间, Timer
会触发,并且往 channel
写入当前时间点,此时 Timer
不再计时。当应用层重新调用 Reset
函数,才又开始计时,这个是和 Tick
不同的。 Tick
是周期性的计时。
Timer
还支持计时结束时,触发自定义函数。 AfterFunc
会返回 Timer
实例。 AfterFunc
调用后,只会计时结束后触发一次自定义函数调用,如果需要再次触发,需要显示调用 Timer.Reset
函数。
如果结束定时器,调用 Stop
即可。
Ticker 运行机制
在 go 源码的 time/tick.go
中,可以看到 NewTicker
实现
func NewTicker(d Duration) *Ticker {if d <= 0 {panic(errors.New("non-positive interval for NewTicker"))}// Give the channel a 1-element time buffer.// If the client falls behind while reading, we drop ticks// on the floor until the client catches up.c := make(chan Time, 1)t := &Ticker{C: c,r: runtimeTimer{when: when(d),period: int64(d),f: sendTime,arg: c,},}startTimer(&t.r)return t
}
可以看到 channel
是带有1个元素的缓冲区。重点关注 runtimeTimer
的定义
when
何时触发定时器。所有的时间值都是纳秒级别,int64
表示。period
: 触发周期。根据此值计算下一次触发时间点, 可以简单理解成when = when + period
。当前时间触发后,会更新when
值。f
: 定时器触发时,调用的函数。注意这个函数必须是非阻塞的,否则会阻塞整个timer
的执行。arg
: 调用函数f
时,传入的参数值。
看到 Ticker
对应的触发函数是 sendTime
, 函数实现如下(time/sleep.go
):
func sendTime(c interface{}, seq uintptr) {// Non-blocking send of time on c.// Used in NewTimer, it cannot block anyway (buffer).// Used in NewTicker, dropping sends on the floor is// the desired behavior when the reader gets behind,// because the sends are periodic.select {case c.(chan Time) <- Now():default:}
}
会把当前的时间点放入到 channel
中,如果应用层没有及时获取 channel
中的值,会直接丢弃当前的时间点,走 default
逻辑。
startTimer
是开启了定时器,此时定时器启动执行。 startTimer
相应代码在 runtime/time.go
中。startTimer -> addtimer -> addtimerLocked
, 具体实现在 addtimerLocked
中。
func addtimerLocked(t *timer) { // when must never be negative; otherwise timerproc will overflow// during its delta calculation and never expire other runtime timersif t.when < 0 {t.when = 1 << 63 - 1}t.i = len(timers.t)timers.t = append(timers.t, t)siftupTimer(t.i)if t.i == 0 {// siftup moved to top: new earliest deadlineif timers.sleeping {timers.sleeping = falsenotewakeup(&timers.waitnote)}if timers.rescheduling {timers.rescheduling = falsegoready(timers.gp, 0)}}if !timers.created {timers.created = truego timerproc()}
}
timers
是全局变量,管理所有的 timer
, 使用数组维护的。数组中的 timer
是有序的,使用了堆排序算法,把 when
最小值排到数组前面,也就是把最先触发定时的 timer
排在最前。
每次新增定时器 timer
时,会调用 siftupTimer
调整数组顺序。
如果当前 timers
是空数组,需要调整 timers
状态, sleeping =false, rescheduling =false
, 如果 timerproc
的 goroutine 为 idle
状态,进行唤醒。调用 goready
函数。
如果 timers
是初次创建,会调用 timerproc
,定时器逻辑全在这里实现。
timerproc
死循环执行, 执行逻辑如下
- 获取当前时间点,纳秒级别
- 如果
timers
数组为空,把执行timerproc
的goroutine
置为idle
状态,节省资源,不必要的空转 - 如果
timers
非空,取出timers
数组中的第一个值,与now
比较timer
的when
值, 如果when
值大,说明timers
中的所有定时器都还未触发,timerproc
的goroutine sleep
, 直到when
值时刻 - 如果数组第一值的
when < now
, 说明已经到了触发时间点,如果timer
的period
有值,说明是周期性触发,更新timer
下次触发时间点
delta = t.when - now
t.when += t.period * (1 + -delta/t.period)
siftdownTimer(0)
下次的时间点,不是简单的 t.when += t.period
, 而是在 0 ~ period
之间。考虑调度因素,选择 0 ~ period
更合理,保证在 period
内会触发。siftdownTimer
使用堆排序算法调整 timer
顺序, timer
的 when
值增加了,可能需要下沉,排在数组后面。
- 如果没有设置
period
值,则移除timers
数组 - 调用
timer.f
函数,当然,timer.arg
是其中的参数
f := t.farg := t.argseq := t.seqf(arg, seq)
Ticker.Stop
函数最终实现对应 runtime/time.go
中的 deltimer
函数。实际就是把 timer
从 timers
数组中删除,删除之后,还需要调整 timers
的数组顺序。
Timer 的运行机制
Timer
和 Ticker
底层实现走的是同一样一套逻辑。 NewTimer
定义在 runtime/sleep.go
中实现
func NewTimer(d Duration) *Timer {c := make(chan Time, 1)t := &Timer{C: c,r: runtimeTimer{when: when(d),f: sendTime,arg: c,},}startTimer(&t.r)return t
}
和 Tick
定义类似,但缺少了 period
的定义。如果定时器触发了, Timer
会被 timers
移除,不在 timers
数组中运行。如果需要运行,需要再次调用 Reset
函数。这里可以看到,触发的函数也是 sendTime
, 和 Tick
是一样的。
func (t *Timer) Reset(d Duration) bool {if t.r.f == nil {panic("time: Reset called on uninitialized Timer")}w := when(d)active := stopTimer(&t.r)t.r.when = wstartTimer(&t.r)return active
}
Reset
主要是重新计算了 when
值,加入到了 timers
数组中,等待再次触发。
Timer
还支持自定义的函数处理。
func AfterFunc(d Duration, f func()) *Timer {t := &Timer{r: runtimeTimer{when: when(d),f: goFunc,arg: f,},}startTimer(&t.r)return t
}func goFunc(arg interface{}, seq uintptr) {go arg.(func())()
}
如果使用 AfterFunc
, 定时器触发时会调用 goFunc
函数,参数 arg
就是我们在 AfterFunc
中自定义的函数 f
。 AfterFunc
返回 Timer
实例,这样可以显示调用 Stop
进行关闭定时器,或者调用 Reset
也能再次触发。
Ticker 和 Timer 的区别
- 如果是周期性的调用,推荐使用
Ticker
, 性能更高,实现更简单 - 如果只是偶尔的触发定时器,使用
Timer
,更节省资源,就算不调用Stop
,触发一次后,也不会一直存在在timers
数组中 Timer
使用更灵活些,支持自定义函数的场景Ticker
需要关注资源泄露的情况,如果Ticker
不在使用,要显示调用Stop
,否则会一直存在在timers
数组中
Sleep 实现机制
sleep
底层实现在 runtime/time.go
函数中,对应函数为 timeSleep
。
func timeSleep(ns int64) {if ns <= 0 {return}t := getg().timerif t == nil {t = new(timer)getg().timer = t}*t = timer{}t.when = nanotime() + nst.f = goroutineReadyt.arg = getg()lock(&timers.lock)addtimerLocked(t)goparkunlock(&timers.lock, "sleep", traceEvGoSleep, 2)
}
也是开启了一个计时器,计算了触发时间值 when
, 对应的触发函数 goroutineReady
, 触发时会把 goroutine
的等待状态变为运行状态。 goparkunlock
会把当前的 goroutine
变为等待状态。
缺少 period
的定义,这样也是触发一次,会在 timers
数组中移除。
总结
- golang timer 在底层实现上,支持纳秒级别
- 各个 timer 不是单独进行系统调用获取时间,而是
timers
统一调用,性能更高 - 触发时间点尽力保证在
period
内,如果period
比较小,在高CPU
压力下,也很难保证。这种情况下,看到的现象是,有时会隔一段时间(比period
长)触发,但是下次触发非常快,因为这时,下次的when
值不会更新 - 已经存在
timer
的情况下,调低period
或者duration
值,性能影响比较小 - 如果会产生大量
timer
的情况下,性能比较差,数组元素多,每次都要进行堆排序算法调整 - Timer 的启动或者
Reset
会计算when
值,这时会获取系统函数,大量使用Timer
性能会比较差 - Ticker 要注意资源泄露,不使用的情况需要及时
Stop
,否则会一直存在在数组中
借鉴:https://studygolang.com/articles/18404
https://www.jianshu.com/p/2c0f8ff98618
https://my.oschina.net/renhc/blog/3037760
仅供学习使用!
Goland中time.Timer and time.Ticker相关推荐
- ASP.NET AJAX入门系列(11):在多个UpdatePanle中使用Timer控件
本文将使用Timer控件更新两个UpdatePanel控件,Timer控件将放在UpdatePanel控件的外面,并将它配置为UpdatePanel的触发器,翻译自官方文档.<?XML:NAME ...
- java捕获定时器抛出的异常_详细了解Java中定时器Timer的使用及缺陷分析
在需要定时并且周期执行任务时,在最初的JAVA工具类库中,Timer可以实现任务的定时周期执行的需求,不过有一定的缺陷,比如,Timer是基于绝对时间而非相对时间,因此Timer对系统时钟比较敏感,本 ...
- Metro App中使用Timer
在设定的时间结束后,执行相应的方法. 使用metro的Windows.System.Threading命名空间下的ThreadPoolTimer类,可以创建一个Timer. Creat ...
- Boost中的Timer的使用——计算时间流逝
使用Boost中的Timer库计算程序的运行时间 程序开发人员都会面临一个共同的问题,即写出高质量的代码完毕特定的功能.评价代码质量的一个重要标准就是算法的运行效率,也就是算法的运行时间.为了可靠的提 ...
- python threading模块中的timer_threading中定时器Timer方法
threading中定时器Timer 定时器功能:在设置的多少时间后执行任务,不影响当前任务的执行 常用方法 from threading import Timer t = Timer(interva ...
- C#windows服务中的Timer控件的使用
C#windows服务程序中的Timer控件的使用是什么情况呢?那么本文就C#windows服务程序中的Timer控件的使用问题向你介绍相关的操作. C# windows服务程序中的Timer控件的使 ...
- Goland中在文件模板中为go文件添加个人声明
Goland中在文件模板中为go文件添加个人声明 打开文件模板菜单 修改内容如下: 效果演示 打开文件模板菜单 从goland左上角依次点击: [File] – [Settings] – [Edito ...
- PB中的timer事件
前言 在软件开发中,我们经常会用到实时获取服务器时间的功能,在这个功能中我们会用到timer控件,来看一下在PB中的timer是如何使用的吧. 实现步骤 1.窗体布局 2.窗体的open事 ...
- JDK中的Timer和TimerTask详解 目录结构: Timer和TimerTask 一个Timer调度的例子 如何终止Timer线程 关于cancle方式终止线程 反复执行一个任务 sche
JDK中的Timer和TimerTask详解 目录结构: Timer和TimerTask 一个Timer调度的例子 如何终止Timer线程 关于cancle方式终止线程 反复执行一个任务 schedu ...
最新文章
- 【学术相关】研究生、博士生全程只靠自己能否发一篇 SCI?
- js实现php中sleep()延时的功能
- JavaScript学习随记——数组二
- mysql首次_mysql首次登陆任务
- javascript 中面向对象实现 如何继承
- 给Jquery easyui 的datagrid 每行添加操作链接
- a = b(将 b 赋值给 a 的另类实现)
- 穷举法求最大公共子序列C语言,算法--最长公共子序列(LongestCommon Subsequence, LCS)...
- winform把html一起生成,联合 MSHTML 与 WebBrowser 生成美观实用的 WinForm 利用过程。...
- 通过SQL语句建立数据库. 表
- arcgis10.2之Maplex(高级标注扩展模块)
- csps2019格雷码
- x星球出入站(蓝桥杯递归)
- 传递VB数组给DLL中的函数
- nodejs mysql 耗硬盘_nodejs操作MySQL其实很简单
- c语言用字符画一个椭圆,c语言,绘制椭圆并使其旋转.doc
- 计算机计算编码知识题库,计算机基础知识复习题库
- 如何制作一寸、二寸、六寸照片。以后不用再去照相馆了!
- 这个社会穷人的出路在哪里?
- 网站搭建的理解与流程
热门文章
- 局域网内共享打印机的几种方式
- 取回Apple TV遥控器的D-Pad
- awl 多线程SYN***工具0.2版[转]
- (div,p)等标签之间“分割线”的两种实现方式
- 各大邮箱网址用哪个好?企业内部邮箱哪个比较好用?
- eclipse的jdt简介
- 猜一宋词名句 Java_古诗词名言名句大全之宋词名句集锦
- 【QA】数学符号 word输入问题 在word里面怎么输入字母头顶上的那个小尖儿
- java线上CPU、内存打满处理
- 蛋白质女孩---走出软件作坊:三五个人十来条枪 如何成为开发正规军(三十)