当前位置: 首页 > news >正文

深入 Go 底层原理(十四):timer 的实现与高性能定时器

1. 引言

time 包中的 TimerTicker 是 Go 中实现定时和周期性任务的基础工具。time.Sleep 相对简单,但 TimerTicker 的背后,是一套由 runtime 管理的高性能、可扩展的定时器系统。

本文将深入 runtime 的定时器实现,了解它是如何用一个**最小堆(Min-Heap)**高效地管理成千上万个定时器的。

2. timer 的核心数据结构

每个通过 time.NewTimertime.NewTicker 创建的定时器,在 runtime 中都对应一个 timer 结构体:

// src/runtime/time.go
type timer struct {when   int64 // 定时器被唤醒的时间点 (纳秒)period int64 // 周期性定时器的时间间隔,对于 Timer 来说是 0f      func(interface{}, uintptr) // 定时器触发时要执行的回调函数arg    interface{} // 回调函数的参数seq    uintptr// 定时器在堆中的索引,用于高效地调整和删除heap_idx int
}

所有待处理的 timer 都会被存放在一个全局的定时器最小堆中。这个堆是根据 when 字段来组织的,即唤醒时间最早的 timer 会在堆顶

3. 早期实现 (Go 1.9 及之前)
  • 使用一个全局的、由互斥锁保护的最小堆。

  • runtime.sysmon 监控线程会定期检查堆顶的 timer

  • 缺点:所有对定时器的操作(添加、删除、重置)都需要获取全局锁,当定时器数量巨大时,锁竞争会成为性能瓶颈。

4. 现代实现 (Go 1.10 ~ Go 1.13) - 四叉堆优化

为了减少锁竞争,Go 1.10 引入了分片锁的思想。它将一个大的全局堆拆分成了 64 个小的、各自有锁的四叉堆(4-heap)

  • 当添加一个定时器时,会根据当前 goroutine 所在的 P (Processor) 的 ID,将其分配到对应的四叉堆中。

  • sysmon 会遍历这 64 个堆,找出最早需要唤醒的 timer

  • 优点:显著降低了锁冲突的概率。

5. 最新实现 (Go 1.14+) - P 私有定时器堆

为了追求极致性能,Go 1.14 再次重构了定时器系统,将定时器与 P 进行了更紧密的绑定。

  • 每个 P (Processor) 都有一个自己的、无锁的定时器最小堆

  • 当一个 goroutine 在某个 P 上创建一个 timer 时,这个 timer 会被直接加入到该 P 的私有堆中。此过程完全无锁

  • 每个 P 在其调度循环中,会自己检查其私有堆的堆顶 timer 是否到期。

  • 此外,还有一个专门的**网络轮询器(netpoller)**线程,它会在没有 P 运行时,负责检查和触发全局的、已到期的定时器。

工作流程:

  1. time.NewTimer 创建一个 timer,并将其加入当前 P 的私有堆。

  2. P 的调度器在执行 Goroutine 的间隙,会检查自己私有堆的堆顶 timer

  3. 如果 timer 到期,P 会将其移出堆,并将其回调函数 f 封装成一个新的 goroutine 去执行。

  4. 如果一个 P 长时间空闲,它会将其私有的 timer 批量转移到一个全局的、带锁的堆中,交由 netpoller 统一处理。

这种设计将绝大多数定时器操作本地化到了 P 上,实现了无锁化,极大地提升了高并发下定时器的性能和可扩展性。

6. 实践陷阱:time.After 与内存泄漏

time.After(d) 本质上是 time.NewTimer(d).C 的语法糖。如果在 for 循环中不加控制地使用 time.After,每次循环都会创建一个新的 Timer 对象。即使 <-time.After(d) 因为其他 case 满足而没有执行,这个 Timer 对象仍然存在于 runtime 的定时器堆中,直到其到期后才会被 GC。这会造成大量的、不必要的内存和计算资源浪费。

正确做法:在循环中使用 time.NewTimer,并通过 timer.Reset() 来复用同一个定时器。

// 错误示例
for {select {case <-ch:// ...case <-time.After(time.Second): // 每次循环都创建新 Timerreturn}
}// 正确示例
timer := time.NewTimer(time.Second)
defer timer.Stop()
for {select {case <-ch:// ...case <-timer.C:return}
}
http://www.dtcms.com/a/311417.html

相关文章:

  • python JSONPath 表达式生成器
  • 淘宝获取商品SKU详情API接口操作指南
  • 交互 Codeforces Round 1040 Interactive RBS
  • 开发指南128-基础类-BaseDAO
  • 力扣面试150题--回文数
  • ABP VNext + NATS JetStream:高性能事件流处理
  • FPGA kernel 仿真器调试环境搭建
  • 分类任务当中常见指标 F1分数、recall、准确率分别是什么含义
  • 「iOS」————SideTable
  • 基于Dockerfile 部署一个 Flask 应用
  • WAIC引爆AI,智元机器人收购上纬新材,Geek+上市,157起融资撑起热度|2025年7月人工智能投融资观察 · 极新月报
  • 【传奇开心果系列】Flet框架流式输出和实时滚动页面的智能聊天机器人自定义模板
  • github在界面创建tag
  • 性能测试-性能测试中的经典面试题二
  • 超级人工智能+无人机操控系统,振兴乡村经济的加速器,(申请专利应用),严禁抄袭!
  • spring-ai-alibaba 学习(十九)——graph之条件边、并行节点、子图节点
  • linux编译基础知识-库文件标准路径
  • Docker 的网络模式
  • 3 使用 Jenkins 构建镜像:将你的应用打包成镜像
  • 【20min 急速入门】使用Demucs进行音轨分离
  • ffmpeg命令和ffplay命令详解
  • Java高性能编程实践指南
  • ARM Cortex-M异常处理高级特性详解
  • OpenCV 全解读:核心、源码结构与图像/视频渲染能力深度对比
  • [硬件电路-121]:模拟电路 - 信号处理电路 - 模拟电路中常见的难题
  • 网络编程之原始套接字
  • Anthropic:跨越生产效能拐点的AI增长飞轮
  • [硬件电路-123]:模拟电路 - 信号处理电路 - 常见的高速运放芯片、典型电路、电路实施注意事项
  • 淘宝小程序的坑
  • 阿里云部署微调chatglm3