编程沉思录

编程沉思录

马上订阅 编程沉思录 RSS 更新: https://www.cyhone.com/atom.xml

Golang 定时器底层实现深度剖析

2020年6月19日 18:00

本文将基于 Golang 源码对 Timer 的底层实现进行深度剖析。主要包含以下内容:

  1. Timer 和 Ticker 在 Golang 中的底层实现细节,包括数据结构等选型。
  2. 分析 time.Sleep 的实现细节,Golang 如何实现 Goroutine 的休眠。

注:本文基于 go-1.13 源码进行分析,而在 go 的 1.14 版本中,关于定时器的实现略有一些改变,以后会再专门写一篇文章进行分析。

概述

我们在日常开发中会经常用到 time.NewTicker 或者 time.NewTimer 进行定时或者延时的处理逻辑。

Timer 和 Ticker 在底层的实现基本一致,本文将主要基于 Timer 进行探讨研究。Timer 的使用方法如下:

1
2
3
4
5
6
7
8
9
10
import (
"fmt"
"time"
)

func main() {
timer := time.NewTimer(2 * time.Seconds)
<-timer.C
fmt.Println("Timer fired")
}

在上面的例子中,我们首先利用 time.NewTimer 构造了一个 2 秒的定时器,同时使用 <-timer.C 阻塞等待定时器的触发。

Timer 的底层实现

对于 time.NewTimer 函数,我们可以轻易地在 go 源码中找到它的实现,其代码位置在 time/sleep.go#L82。如下:

time/sleep.go
1
2
3
4
5
6
7
8
9
10
11
12
13
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
}

NewTimer 主要包含两步:

  1. 创建一个 Timer 对象,主要包括其中的 C 属性和 r 属性。r 属性是 runtimeTimer 类型。
  2. 调用 startTimer 函数,启动 timer。

在 Timer 结构体中的属性 C 不难理解,从最开始的例子就可以看到,它是一个用来接收 Timer 触发消息的 channel。注意,这个 channel 是一个有缓冲 channel,缓冲区大小为 1。

我们主要看的是 runtimeTimer 这个结构体:

  1. when: when 代表 timer 触发的绝对时间。计算方式就是当前时间加上延时时间。
  2. f: f 则是 timer 触发时,调用的 callback。而 arg 就是传给 f 的参数。在 Ticker 和 Timer 中,f 都是 sendTime。

timer 对象构造好后,接下来就调用了 startTimer 函数,从名字来看,就是启动 timer。具体里面做了哪些事情呢?

startTimer 具体的函数定义在 runtime/time.go 中,里面实际上直接调用了另外一个函数 addTimer...

剩余内容已隐藏

查看完整文章以阅读更多