Go语言定时器实现原理及作用
对于任何一个正在运行的应用,如何获取准确的绝对时间都非常重要,但是在一个分布式系统中我们很难保证各个节点上绝对时间的一致性,哪怕通过 NTP 这种标准的对时协议也只能把时间的误差控制在毫秒级,所以相对时间在一个分布式系统中显得更为重要,在接下来的讲解中我们将会介绍一下Go语言中的定时器以及它在并发编程中起到什么样的作用。
绝对时间一定不会是完全准确的,它对于一个运行中的分布式系统其实没有太多指导意义,但是由于相对时间的计算不依赖于外部的系统,所以它的计算可以做的比较准确,首先介绍一下Go语言中用于计算相对时间的定时器的实现原理。
结构
timer 就是Go语言定时器的内部表示,每一个 timer 其实都存储在堆中,tb 就是用于存储当前定时器的桶,而 i 是当前定时器在堆中的索引,我们可以通过这两个变量找到当前定时器在堆中的位置:
type timer struct {
tb *timersBucket
i int
when int64
period int64
f func(interface{}, uintptr)
arg interface{}
seq uintptr
}
when 表示当前定时器(Timer)被唤醒的时间,而 period 表示两次被唤醒的间隔,每当定时器被唤醒时都会调用 f(args, now) 函数并传入 args 和当前时间作为参数。
然而这里的 timer 作为一个私有结构体其实只是定时器的运行时表示,time 包对外暴露的定时器使用了如下所示的结构体:
type Timer struct {
C <-chan Time
r runtimeTimer
}
Timer 定时器必须通过 NewTimer 或者 AfterFunc 函数进行创建,其中的 runtimeTimer 其实就是上面介绍的 timer 结构体,当定时器失效时,失效的时间就会被发送给当前定时器持有的 Channel C,订阅管道中消息的 Goroutine 就会收到当前定时器失效的时间。
在 time 包中,除了 timer 和 Timer 两个分别用于表示运行时定时器和对外暴露的 API 之外,timersBucket 这个用于存储定时器的结构体也非常重要,它会存储一个处理器上的全部定时器,不过如果当前机器的核数超过了 64 核,也就是机器上的处理器 P 的个数超过了 64 个,多个处理器上的定时器就可能存储在同一个桶中:
type timersBucket struct {
lock mutex
gp *g
created bool
sleeping bool
rescheduling bool
sleepUntil int64
waitnote note
t []*timer
}
每一个 timersBucket 中的 t 就是用于存储定时器指针的切片,每一个运行的Go语言程序都会在内存中存储着 64 个桶,这些桶中都存储定时器的信息:
发表评论