golang定时器的精度

以 go1.23.3 linux/amd64 为例。

定时器示例代码:

package mainimport ("context""fmt""time"
)var ctx context.Contextfunc main() {timeout := 600 * time.Secondctx, _ = context.WithTimeout(context.Background(), timeout)deadline, _ := ctx.Deadline()fmt.Println("process start", time.Now().Format(time.DateTime))fmt.Println("ctx deadline", deadline.Format(time.DateTime))go func() {defer func() {fmt.Println("goroutine exit", time.Now().Format(time.DateTime))}()for {select {case <-ctx.Done():fmt.Println("ctx.Done", time.Now().Format(time.DateTime))returndefault:fmt.Println("do something start", time.Now().Format(time.DateTime))time.Sleep(60 * time.Second)fmt.Println("do something end  ", time.Now().Format(time.DateTime))}}}()time.Sleep(timeout + 10*time.Second)fmt.Println("process exit", time.Now().Format(time.DateTime))
}

定时器创建流程:

定时器的类型为:runtime.timer,新建定时器会调用runtime.newTimer函数。

runtime.newTimer函数会调用func (t *timer) maybeAdd(),在此函数中将定时器放入ts堆中:

func (t *timer) maybeAdd() {// Note: Not holding any locks on entry to t.maybeAdd,// so the current g can be rescheduled to a different M and P// at any time, including between the ts := assignment and the// call to ts.lock. If a reschedule happened then, we would be// adding t to some other P's timers, perhaps even a P that the scheduler// has marked as idle with no timers, in which case the timer could// go unnoticed until long after t.when.// Calling acquirem instead of using getg().m makes sure that// we end up locking and inserting into the current P's timers.mp := acquirem()ts := &mp.p.ptr().timersts.lock()ts.cleanHead()t.lock()t.trace("maybeAdd")when := int64(0)wake := falseif t.needsAdd() {t.state |= timerHeapedwhen = t.whenwakeTime := ts.wakeTime()wake = wakeTime == 0 || when < wakeTimets.addHeap(t)}t.unlock()ts.unlock()releasem(mp)if wake {wakeNetPoller(when)}
}

入堆:

// addHeap adds t to the timers heap.
// The caller must hold ts.lock or the world must be stopped.
// The caller must also have checked that t belongs in the heap.
// Callers that are not sure can call t.maybeAdd instead,
// but note that maybeAdd has different locking requirements.
func (ts *timers) addHeap(t *timer) {assertWorldStoppedOrLockHeld(&ts.mu)// Timers rely on the network poller, so make sure the poller// has started.if netpollInited.Load() == 0 {netpollGenericInit()}if t.ts != nil {throw("ts set in timer")}t.ts = tsts.heap = append(ts.heap, timerWhen{t, t.when})ts.siftUp(len(ts.heap) - 1)if t == ts.heap[0].timer {ts.updateMinWhenHeap()}
}

timers堆为每P持有,保存P队列中协程定义的定时器。

// A timers is a per-P set of timers.
type timers struct {// mu protects timers; timers are per-P, but the scheduler can// access the timers of another P, so we have to lock.mu mutex// heap is the set of timers, ordered by heap[i].when.// Must hold lock to access.heap []timerWhen// len is an atomic copy of len(heap).len atomic.Uint32// zombies is the number of timers in the heap// that are marked for removal.zombies atomic.Int32// raceCtx is the race context used while executing timer functions.raceCtx uintptr// minWhenHeap is the minimum heap[i].when value (= heap[0].when).// The wakeTime method uses minWhenHeap and minWhenModified// to determine the next wake time.// If minWhenHeap = 0, it means there are no timers in the heap.minWhenHeap atomic.Int64// minWhenModified is a lower bound on the minimum// heap[i].when over timers with the timerModified bit set.// If minWhenModified = 0, it means there are no timerModified timers in the heap.minWhenModified atomic.Int64
}type timerWhen struct {timer *timerwhen  int64
}

创建定时器堆栈如图:

定时器触发流程:

timers堆的定时器通过func (ts *timers) run(now int64) int64出堆并运行。

而检查是否有定时器到期是通过函数func (ts *timers) check(now int64) (rnow, pollUntil int64, ran bool)中的func (ts *timers) wakeTime() int64进行的。

check函数和wakeTime函数的调度时机在runtime/proc.go文件中多处存在,如runtime.findRunnable()、runtime.stealWork(now int64)、runtime.schedule()等。

这种依赖协程调度、系统调用等触发的定时器检查,延迟时间最多可达到func sysmon()协程的间隔时间10ms。

触发定时器堆栈如图:

另外在新建定时器时,也会检查timers堆顶部的定时器剩余时间,如果已经到期也会立刻通过runtime.wakeNetPoller(when int64)触发runtime.netpoll(delay int64)返回,检查是否存在可处理的event,然后进行timers堆的定时器check。

定时器精度小结:

golang内置的Timer定时器维护在用户态,比较轻量,依赖协程调度、系统调用、event等来触发时间到期检查,延迟在10ms以内,精度不高。

定时器的观测:

修改源码创建多个ctx定时器:

package mainimport ("context""fmt""time"
)var ctx context.Contextfunc main() {timeout := 300 * time.Secondctx, _ = context.WithTimeout(context.Background(), timeout)ctx, _ = context.WithTimeout(ctx, 180*time.Second)deadline, _ := ctx.Deadline()fmt.Println("process start", time.Now().Format(time.DateTime))fmt.Println("ctx deadline", deadline.Format(time.DateTime))go func() {defer func() {fmt.Println("goroutine exit", time.Now().Format(time.DateTime))}()for {select {case <-ctx.Done():fmt.Println("ctx.Done", time.Now().Format(time.DateTime))returndefault:fmt.Println("do something start", time.Now().Format(time.DateTime))time.Sleep(5 * time.Second)fmt.Println("do something end  ", time.Now().Format(time.DateTime))}}}()time.Sleep(timeout + 10*time.Second)fmt.Println("process exit", time.Now().Format(time.DateTime))
}

dlv调试:

1、查看当前的定时器数量:

p runtime.allp[1].timers.heap 

2、查看每个定时器的超时时间:

p (runtime.allp[1].timers.heap[0].when - time.startNano)/int64(time.Second)
p (runtime.allp[1].timers.heap[0].when - time.startNano)/int64(time.Second)
p (runtime.allp[1].timers.heap[0].when - time.startNano)/int64(time.Second)

3、调用其中一个定时器的回调函数:

call runtime.allp[1].timers.heap[0].timer.arg.(func())()

4、查看控制台输出:

共有3个定时器,分别是ctx的2个和主协程的time.Sleep,其中timers堆顶是180s的定时器。

在手工调用timers堆顶定时器的回调函数后,提前收到ctx.Done通知,程序提前退出。

如图:

cancelCtx的父子关系:

继续上面的例子:

1、查看ctx两个定时器的回调函数是否一致:

p runtime.allp[1].timers.heap[0].timer.arg
p runtime.allp[1].timers.heap[1].timer.arg

2、查看父子cancelCtx变量内容:

#子ctx
p ctx
#父ctx
p ctx.cancelCtx.Context

3、观测结果说明:

父子cancelCtx回调函数内部引用的外部变量context.timerCtx并不相同。

父子cancelCtx之间是嵌套关系,子嵌套(继承)父。

最终使用的ctx为子ctx,子ctx的任一层父ctx的超时都会导致子ctx退出。

如图:

父子cancelCtx的嵌套关系通过函数func (c *cancelCtx) propagateCancel(parent Context, child canceler)完成:

// propagateCancel arranges for child to be canceled when parent is.
// It sets the parent context of cancelCtx.
func (c *cancelCtx) propagateCancel(parent Context, child canceler) {c.Context = parentdone := parent.Done()if done == nil {return // parent is never canceled}select {case <-done:// parent is already canceledchild.cancel(false, parent.Err(), Cause(parent))returndefault:}if p, ok := parentCancelCtx(parent); ok {// parent is a *cancelCtx, or derives from one.p.mu.Lock()if p.err != nil {// parent has already been canceledchild.cancel(false, p.err, p.cause)} else {if p.children == nil {p.children = make(map[canceler]struct{})}p.children[child] = struct{}{}}p.mu.Unlock()return}if a, ok := parent.(afterFuncer); ok {// parent implements an AfterFunc method.c.mu.Lock()stop := a.AfterFunc(func() {child.cancel(false, parent.Err(), Cause(parent))})c.Context = stopCtx{Context: parent,stop:    stop,}c.mu.Unlock()return}goroutines.Add(1)go func() {select {case <-parent.Done():child.cancel(false, parent.Err(), Cause(parent))case <-child.Done():}}()
}

--end--

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/78879.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

svn 远程服务搜索功能

svn服务器没有远程搜索功能&#xff0c;靠人工检索耗时耗力&#xff0c;当服务器文件过多时&#xff0c;全部checkout到本地检索&#xff0c;耗时太久。 1. TortoiseSVN 安装注意事项 下载官网地址&#xff1a;https://tortoisesvn.en.softonic.com/download 安装时选中 co…

uniapp-商城-39-shop 购物车 选好了 进行订单确认4 配送方式2 地址页面

上面讲基本的样式和地址信息&#xff0c;但是如果没有地址就需要添加地址&#xff0c;如果有不同的地址就要选地址。 来看看处理方式&#xff0c; 1、回顾 在delivery-layout中 methods:{goAddress(){uni.navigateTo({url:"/pagesub/pageshop/address/addrlist"})…

Linux命令-iostat

iostat 命令介绍 iostat 是一个用于监控 Linux 系统输入/输出设备加载情况的工具。它可以显示 CPU 的使用情况以及设备和分区的输入/输出统计信息&#xff0c;对于诊断系统性能瓶颈&#xff08;如磁盘或网络活动缓慢&#xff09;特别有用。 语法&#xff1a; iostat [options…

vue2关于Node.js17及以上报digital envelope错误的解决办法

文章目录 简介错误原因解决方案设置环境变量修改package.json安装旧版本Node.js更新依赖项更改加密设置 简介 digital envelope routines::unsupported错误‌通常发生在Node.js版本升级到17或更高版本后&#xff0c;因为这些版本开始使用OpenSSL 3.0&#xff0c;它对算法和密钥…

LLM - Large Language Model

回顾2024&#xff1a;与LLM又相伴一年的经历与思考 - 知乎万字长文入门大语言模型&#xff08;LLM&#xff09; - 知乎“大模型本质就是两个文件&#xff01;”特斯拉前AI总监爆火LLM科普&#xff0c;时长1小时&#xff0c;面向普通大众 - 知乎大模型本质及趋势剖析&#xff0c…

Linux 内核网络协议栈中的关键数据结构:inet_skb_parm 与 ip_options

在 Linux 内核的网络协议栈中,数据包的高效处理依赖于一系列精心设计的数据结构。这些结构体不仅需要存储网络数据的元信息,还需支持复杂的协议逻辑(如路由、分片、安全策略等)。本文聚焦两个核心结构体 struct inet_skb_parm 和 struct ip_options,解析它们的设计原理、功…

如何修复卡在恢复模式下的 iPhone:简短指南

Apple 建议使用恢复模式作为最后的手段&#xff0c;以便在 iPhone 启动循环或显示 Apple 标志时恢复 iPhone。这是解决持续问题的简单方法&#xff0c;但您很少使用。但是&#xff0c;当您的 iPhone 卡住恢复模式本身时&#xff0c;您会怎么做&#xff1f;虽然 iPhone 卡在这种…

10前端项目----商品详情页/滚轮行为

商品详情页面 商品详情组件发送请求获取相应商品详情信息组件展示数据 优化一下路由配置代码滚轮自动置顶 商品详情组件 路由配置 点击商品进行跳转—将Detail组件变成路由组件 从商品到详情&#xff0c;肯定需要传参(产品ID)告诉Detail是哪个商品&#xff0c;需要展示哪个商品…

DIFY 又跟新了,来到 1.3.0 版本,看正文

欢迎来到 1.3.0 版本&#xff01;添加了各种巧妙的功能、修复了错误&#xff0c;并带来了一些新功能&#xff1a; 一、核心亮点&#xff1a; 结构化输出 1、LLM 节点新增JSON Schema编辑器&#xff0c;确保大语言模型能够返回符合预设格式的JSON数据。这一功能有助于提升数据…

git检查提交分支和package.json的version版本是否一致

这里写自定义目录标题 一、核心实现步骤‌1.安装必要依赖‌2.初始化 Husky‌3.创建校验脚本‌4.配置 lint-staged‌5.更新 Husky 钩子‌ 三、工作流程说明‌四、注意事项‌ 以下是基于 Git Hooks 的完整解决方案&#xff0c;通过 husky 和自定义脚本实现分支名与版本号一致性校…

react-navigation-draw抽屉导航

心得写在前面分享给大家&#xff1a; 我的实现方法&#xff0c;并没有完全安装官网来做&#xff0c;而是进行了简化&#xff0c;效果是一样的。没有按照官网说的修改metro.config.js文件&#xff0c;同时也没有 react-native-gesture-handler 的安装后&#xff0c;我们需要有条…

【计算机视觉】CV实战项目-高分辨率遥感图像语义分割:High-Resolution-Remote-Sensing-Semantic-Segmentation

高分辨率遥感图像语义分割技术解析与实战指南 项目背景与意义核心技术解析1. **膨胀预测&#xff08;Dilated Prediction&#xff09;**2. **后处理优化**3. **半监督学习&#xff1a;伪标签&#xff08;Pseudo Labeling&#xff09;**4. **可视化与监控** 实战指南&#xff1a…

免费送源码:Java+SSM+MySQL 基于SSM开发的校园心理咨询平台系统的设计与实现 计算机毕业设计原创定制

目 录 1 绪论 1 1.1 研究背景 1 1.2开发现状 1 1.3论文结构与章节安排 2 2 校园心理咨询平台系统系统分析 3 2.1 可行性分析 3 2.1.1 技术可行性分析 3 2.1.2 经济可行性分析 3 2.1.3 法律可行性分析 3 2.2 系统功能分析 3 2.2.1 功能性分析 4 2.2.2 非功能性分析…

学习笔记:Qlib 量化投资平台框架 — GETTING STARTED

学习笔记&#xff1a;Qlib 量化投资平台框架 — GETTING STARTED Qlib 是微软亚洲研究院开源的一个面向人工智能的量化投资平台&#xff0c;旨在实现人工智能技术在量化投资中的潜力&#xff0c;赋能研究&#xff0c;并创造价值&#xff0c;从探索想法到实施生产。Qlib 支持多种…

cmake qt 项目编译

当前MAC 编译命令 rm -rf build 删除之前build记录 mkdir build && cd build 重新生成build文件夹 cmake -DCMAKE_PREFIX_PATH"/usr/local/opt/qt" .. Cmake编译指定我的qt路径 cmake --build . 生成程序 程序生成后如此 第三方库单独下载 在CMakeLis…

Swift与iOS内存管理机制深度剖析

前言 内存管理是每一位 iOS 开发者都绕不开的话题。虽然 Swift 的 ARC&#xff08;自动引用计数&#xff09;极大简化了开发者的工作&#xff0c;但只有深入理解其底层实现&#xff0c;才能写出高效、健壮的代码&#xff0c;避免各种隐蔽的内存问题。本文将从底层原理出发&…

【机器学习】​碳化硅器件剩余使用寿命稀疏数据深度学习预测

2025 年,哈尔滨工业大学的 Le Gao 等人基于物理信息深度学习(PIDL)方法,研究了在稀疏数据条件下碳化硅(SiC)MOSFET 的剩余使用寿命(RUL)预测问题,尤其关注了其在辐射环境下的可靠性。该研究团队通过一系列实验,采用 ⁶⁰Co γ 射线作为辐射源,以 50rad/s 的剂量率照…

Spring Boot API版本控制实践指南

精心整理了最新的面试资料和简历模板&#xff0c;有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 引言 在API迭代过程中&#xff0c;版本控制是保障系统兼容性的重要机制。合理的版本控制策略可以帮助开发团队平滑过渡接口变更&#xff0c;同时支持多版本客…

AI 语音芯片赋能血压计,4G Cat.1语音模组重构血压监测体验,重新定义 “智能健康管理

一、技术升级背景 全球老龄化进程加速与慢性病管理需求激增的背景下&#xff0c;传统血压计面临三大核心痛点&#xff1a; 操作门槛高&#xff1a;老年群体对复杂按键操作适应性差&#xff0c;误触率达42%&#xff08;参考WHO数据&#xff09; 数据孤岛化&#xff1a;87%的居家…

WebServiceg工具

WebServiceg工具 几年前的简单记录一下。 /*** 调用webService 接口返回字符串* param asmxUrl 提供接口的地址 https://app.***.**.cn/Ser.asmx* param waysName 设置要调用哪个方法 上面接口打开后需要调用的方法名字 * param params 请求的参数 参数* return*/…