3.定时器Timer的实现
time.Timer的实现
定时任务是我们工作中经常遇到的一个场景。go 里可以通过 time.Timer / time.Ticker 轻松构建定时器用于任务定时执行。不知大家有没有好奇过定时器是如何实现的?
各版本实现演进
go1.10及之前一个全局最小堆
最小堆是非常常见的用来管理
timer的数据结构。在最小堆中,作为排序依据的key是timer的when属性,也就是何时触发。即最近的timer将会处于堆顶。
再起一个叫
timeproc的协程,通过死循环持续检查堆顶的timer是否到期,并触发对应的回调timeproc执行时需要阻塞(for死循环)占用M,会挤走M上本来运行的G和P,切换代价较高
go1.0 ~ go1.13上述方案存在锁竞争;且堆体积较大,插入 / 删除效率低
将全局最小堆改为
64个全局最小堆(四叉堆)将所有定时器打散到
64个最小堆中,减小每个堆的数据量,插入时用P的id取模找堆,降低锁粒度每个桶一个
timeproc协程,仍然存在上下文切换代价高的问题
go1.14不再使用
64个桶,四叉堆改放到P里但其实之前
64个桶一般也远大于P的数量,因此这方面的提升效果不大
不再使用
timeproc协程调度,改为在调度时触发定时器的检查,避免了timeproc的上下文切换、调度像
G任务一样,当P本地没有timer时,可以尝试从其他的P偷取一些timer
其它方案
时间轮:
kafka使用比如将
1秒 分成长度为1000的环形队列,则每个格子表示1ms。每个格子里用数组(浪费空间)或链表(浪费时间)存放任务再起个死循环进程,判断当前时间所属的格子上,是否有定时任务需要执行
可以像时分秒的设计一样,使用多层时间轮组合,即可组合表示更多的时间
槽多,可以很大程度减少锁竞争
需要预先确定时间间隔和定时器数量,不如最小堆灵活
执行时间较为集中的定时任务时,有踏空问题,即访问到的格子大多是没有任务的,白占用空间
红黑树:
nginx使用最小堆是把最近的
timer放在堆顶,红黑树则是把最近的timer放入树最左侧
树类型的插入和删除时间复杂度为O(lgN),比时间轮慢
对于持续性定时的
ticker类型,只需要将其从堆/队列里取出,调整下次到期时间,再放回去即可,类似永久性递归
以下内容基于 go1.14
1. 底层结构
所有的计时器都以最小四叉堆的形式存储在处理器 runtime.p 中
timer 主要关注 3 个属性:用于通知外层时间的 chan、用于判断触发时间的 when,和回调用的函数 f
time 包提供了2种方法来新建 Timer:time.NewTimer 和 time.AfterFunc
1.1 初始化 NewTimer
NewTimer 里 timer 的回调就是 sendTime,即向 channel 中发送当前时间。
外层监听这个 channel,就可以知道定时器是否到时间了。
timer 对象构造好后,接下来就调用了 startTimer 函数,将 timer 放到当前 P 的时间堆里
runtime.cleantimers
对 P 中 timer 堆的头节点进行清理工作
runtime.doaddtimer
doaddtimer 函数实际上很简单,主要是将 timer 与 P 设置关联关系,并将 timer 加到堆里。
timer 的运行
timer 的运行是交给 runtime.runtimer 函数执行的,这个函数会不断检查 P 上最小堆堆顶 timer 的状态,根据状态做不同的处理。
运行时会根据 period 判断该 timer 是否为 ticker 类型,是否需要反复执行:是的话需要重设下次执行时间,并调整该 timer 在堆中的位置;一次性 timer 的话则会删除该 timer。最后运行 timer 中的回调函数
timer 的触发 / 唤醒
timer 的触发有两种:
从调度循环中触发
调用
runtime.schedule执行调度时调用
runtime.findrunnable获取可执行G/ 执行抢占时
sysmon每轮监控中会触发
runtime.schedule
runtime.sysmon
另外, P 没有 timer 了的时候,也会尝试偷窃其余 P 的 timer 执行。
sleep 的实现
每个
goroutine底层的G对象上,都有一个timer属性,这是个runtimeTimer对象,专门给sleep使用。当第一次调用sleep的时候,会创建这个runtimeTimer,之后sleep的时候会一直复用这个timer对象。调用
sleep时,触发timer后,直接调用gopark,将当前goroutine挂起。它的
callback就是goready,timer到期后触发这个goready回调,该回调内容就是唤醒被挂起的goroutine。
参考
Last updated