Golang Ticker Reset异常的坑

前言

延迟执行的场景我们通常会使用time.NewTimer(…)来实现,当一些场合可能需要使用timer.Reset(…)方法修改超时时间,这时使用要多注意, 使用不当会导致Reset失败,或是重复执行两次的情况。

复现

下面这段代码我们是希望:"fmt.Println(“timeout:”, time.Now())"只是执行一次,然后就是deadlock的panic:

package mainimport ("fmt""time"
)func main() {timer := time.NewTimer(1 * time.Second)c := make(chan struct{}, 1)c <- struct{}{}for {select {case <-c: // 执行一次fmt.Println("start:", time.Now())time.Sleep(2 * time.Second)timer.Reset(5 * time.Second) // 重置前前实际上时间已经到了case <-timer.C:fmt.Println("timeout:", time.Now()) // 只希望执行一次}}
}

但是,实际执行结果如下:

[Running] go run "/Users/hyman/go/src/bsc-flow-backend/internal/infra/redis/locker/main/main.go"
start: 2025-01-27 15:48:06.429385 +0800 CST m=+0.000182501
timeout: 2025-01-27 15:48:08.434472 +0800 CST m=+2.005297501
timeout: 2025-01-27 15:48:13.43967 +0800 CST m=+7.010568084
fatal error: all goroutines are asleep - deadlock!goroutine 1 [select]:
main.main()/Users/hyman/go/src/bsc-flow-backend/internal/infra/redis/locker/main/main.go:13 +0x80
exit status 2[Done] exited with code=1 in 7.469 seconds

"timeout"执行了两次,这并不符合我们的预期。

分析

查看Timer的源码我们可以发现,"C"其实是一个带缓冲为1的chan:

type Timer struct {C <-chan Timer runtimeTimer
}
// NewTimer creates a new Timer that will send
// the current time on its channel after at least duration d.
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
}

当执行时间到了,就往C发送当前的时间,发送使用select default的方式来防止阻塞泄露的问题:

// sendTime does a non-blocking send of the current time on c.
func sendTime(c any, seq uintptr) {select {case c.(chan Time) <- Now():default:}
}

而Reset方法并不会重新创建"C",只是执行restTimer方法重置触发时间:

func (t *Timer) Reset(d Duration) bool {if t.r.f == nil {panic("time: Reset called on uninitialized Timer")}w := when(d)return resetTimer(&t.r, w)
}

一般情况下Reset后执行的符合预期,但是当Reset前触发时间已经到了,这个时候"C"实际上是已经有数据,所以如果读取timer.C时就会立即执行,再当Reset后的时间到达时,又也会再触发一次,所以就会出现上面的情况"timeout"执行两次。

方案

解决方案就是reset前C有值时,就先读取出来。官方文档有强调使用Reset方法需要在定时器已经Stop或是“C”里已经没数据的情况下:

// For a Timer created with NewTimer, Reset should be invoked only on

// stopped or expired timers with drained channels.

同时也给出了使用姿势:

// if !t.Stop() {

// <-t.C

// }

// t.Reset(d)

所以,代码就可以改为:

package mainimport ("fmt""time"
)func main() {timer := time.NewTimer(1 * time.Second)c := make(chan struct{}, 1)c <- struct{}{}for {select {case <-c: // 执行一次fmt.Println("start:", time.Now())time.Sleep(2 * time.Second)if !timer.Stop() { // 返回false表示C里有值<-timer.C}timer.Reset(5 * time.Second) // 重置前前实际上时间已经到了case <-timer.C:fmt.Println("timeout:", time.Now()) // 只希望执行一次}}
}

执行结果:

[Running] go run "/Users/hyman/go/src/bsc-flow-backend/internal/infra/redis/locker/main/main.go"
start: 2025-01-27 16:42:36.051956 +0800 CST m=+0.000152459
timeout: 2025-01-27 16:42:43.062077 +0800 CST m=+7.010485959
fatal error: all goroutines are asleep - deadlock!goroutine 1 [select]:
main.main()/Users/hyman/go/src/bsc-flow-backend/internal/infra/redis/locker/main/main.go:13 +0x80
exit status 2[Done] exited with code=1 in 7.697 seconds

符合预期

结论

timer调用reset前需要先执行stop方法,如果stop返回的false时,就说明定时器已经被触发,需要执行<-timer.C先读出数据

原文地址:https://itart.cn/blogs/2025/practice/ticker-reset-exception.html

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

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

相关文章

Chameleon(变色龙) 跨平台编译C文件,并一次性生成多个平台的可执行文件

地址:https://github.com/MartinxMax/Chameleon Chameleon 跨平台编译C文件&#xff0c;并一次性生成多个平台的可执行文件。可以通过编译Chameleon自带的.C文件反向Shell生成不同平台攻击载荷。 登录 & 代理设置 按照以下步骤设置 Docker 的代理&#xff1a; 创建配置目…

DFFormer实战:使用DFFormer实现图像分类任务(二)

文章目录 训练部分导入项目使用的库设置随机因子设置全局参数图像预处理与增强读取数据设置Loss设置模型设置优化器和学习率调整策略设置混合精度&#xff0c;DP多卡&#xff0c;EMA定义训练和验证函数训练函数验证函数调用训练和验证方法 运行以及结果查看测试完整的代码 在上…

几种K8s运维管理平台对比说明

目录 深入体验**结论**对比分析表格**1. 功能对比****2. 用户界面****3. 多租户支持****4. DevOps支持** 细对比分析1. **Kuboard**2. **xkube**3. **KubeSphere**4. **Dashboard****对比总结** 深入体验 KuboardxkubeKubeSphereDashboard 结论 如果您需要一个功能全面且适合…

DeepSeek API 的获取与对话示例

代码文件下载&#xff1a;Code 在线链接&#xff1a;Kaggle | Colab 文章目录 注册并获取API环境依赖设置 API单轮对话多轮对话流式输出更换模型 注册并获取API 访问 https://platform.deepseek.com/sign_in 进行注册并登录&#xff1a; 新用户注册后将赠送 10 块钱余额&#…

Vue 3 + TypeScript 实现父子组件协同工作案例解析

引言 在现代的前端开发中&#xff0c;Vue.js 作为一款流行的渐进式 JavaScript 框架&#xff0c;为我们构建交互式用户界面提供了强大的支持。Vue 3 的推出带来了许多新特性&#xff0c;尤其是组合式 API 的引入&#xff0c;让代码的组织和复用更加灵活。同时&#xff0c;TypeS…

基于STM32的循迹小车设计与实现

1 系统方案设计 根据系统设计功能&#xff0c;展开基于STM32的循迹小车设计&#xff0c;整体设计框图如图2.1所示。系统采用STM32单片机作为控制器,通过L298驱动器控制两个直流电机实现对小车的运动控制&#xff0c;两路红外模块实现黑线的检测&#xff0c;HC-SR04超声波模块实…

14.模型,纹理,着色器

模型、纹理和着色器是计算机图形学中的三个核心概念&#xff0c;用通俗易懂的方式来解释&#xff1a; 1. 模型&#xff1a;3D物体的骨架 通俗解释&#xff1a; 模型就像3D物体的骨架&#xff0c;定义了物体的形状和结构。 比如&#xff0c;一个房子的模型包括墙、屋顶、窗户等…

Docker/K8S

文章目录 项目地址一、Docker1.1 创建一个Node服务image1.2 volume1.3 网络1.4 docker compose 二、K8S2.1 集群组成2.2 Pod1. 如何使用Pod(1) 运行一个pod(2) 运行多个pod 项目地址 教程作者&#xff1a;教程地址&#xff1a; https://www.bilibili.com/video/BV1Zn4y1X7AZ?…

Ubuntu 20.04安装Protocol Buffers 2.5.0

个人博客地址&#xff1a;Ubuntu 20.04安装Protocol Buffers 2.5.0 | 一张假钞的真实世界 安装过程 Protocol Buffers 2.5.0源码下载&#xff1a;https://github.com/protocolbuffers/protobuf/tree/v2.5.0。下载并解压。 将autogen.sh文件中以下内容&#xff1a; curl htt…

算法每日双题精讲 —— 二分查找(寻找旋转排序数组中的最小值,点名)

&#x1f31f;快来参与讨论&#x1f4ac;&#xff0c;点赞&#x1f44d;、收藏⭐、分享&#x1f4e4;&#xff0c;共创活力社区。 &#x1f31f; 别再犹豫了&#xff01;快来订阅我们的算法每日双题精讲专栏&#xff0c;一起踏上算法学习的精彩之旅吧&#x1f4aa; 在算法的…

【踩坑日常,已解决】彻底修改IDEA项目的JDK版本,8改为17

三处修改彻底解决IDEA中JDK版本不对问题&#xff08;8改为17&#xff09; 文章目录 三处修改彻底解决IDEA中JDK版本不对问题&#xff08;8改为17&#xff09;第一处第二处第三处 第一处 setting -> Build, Execution, Deployment -> Java Compiler -> Target bytecod…

linux naive代理设置

naive linux客户端 Release v132.0.6834.79-2 klzgrad/naiveproxy GitHub Client setup Run ./naive with the following config.json to get a SOCKS5 proxy at local port 1080. {"listen": "socks://127.0.0.1:1080","proxy": "htt…

redis的分片集群模式

redis的分片集群模式 1 主从哨兵集群的问题和分片集群特点 主从哨兵集群可应对高并发写和高可用性&#xff0c;但是还有2个问题没有解决&#xff1a; &#xff08;1&#xff09;海量数据存储 &#xff08;2&#xff09;高并发写的问题 使用分片集群可解决&#xff0c;分片集群…

Nuxt:利用public-ip这个npm包来获取公网IP

目录 一、安装public-ip包1.在Vue组件中使用2.在Nuxt.js插件中使用public-ip 一、安装public-ip包 npm install public-ip1.在Vue组件中使用 你可以在Nuxt.js的任意组件或者插件中使用public-ip来获取公网IP。下面是在一个Vue组件中如何使用它的例子&#xff1a; <template…

AI软件栈:LLVM分析(一)

文章目录 AI 软件栈后端编译LLVM IRLLVM的相关子项目AI 软件栈后端编译 AI软件栈的后端工作通常与硬件架构直接相关,为了实现一个既能适配现代编程语言、硬件架构发展的目标,所以提出了LLVM具备多阶段优化能力提供基础后端描述,便于进行编译器开发兼容标准编译器的行为LLVM …

搭建Spring Boot开发环境

JDK&#xff08;1.8及以上版本&#xff09; Apache Maven 3.6.0 修改settings.xml 设置本地仓库位置 <localRepository>D:/repository</localRepository> 设置远程仓库镜像 <mirror><id>alimaven</id><name>aliyun maven</name&…

智慧校园在职业学校的实施与展望

随着信息技术的发展&#xff0c;智慧校园的概念逐渐走进人们的视野。智慧校园不仅是一个技术层面的概念&#xff0c;更是教育理念的一次革新。对于职业教育而言&#xff0c;智慧校园的应用更是具有重要意义。通过引入物联网、大数据等先进技术&#xff0c;可以实现教学资源的高…

【Flutter】旋转元素(Transform、RotatedBox )

这里写自定义目录标题 Transform旋转元素可以改变宽高约束的旋转 - RotatedBox Transform旋转元素 说明&#xff1a;Transform旋转操作改变了元素的方向&#xff0c;但并没有改变它的布局约束。因此&#xff0c;虽然视觉上元素看起来是旋转了&#xff0c;但它仍然遵循原始的宽…

Excel中LOOKUP函数的使用

文章目录 VLOOKUP&#xff08;垂直查找&#xff09;&#xff1a;HLOOKUP&#xff08;水平查找&#xff09;&#xff1a;LOOKUP&#xff08;基础查找&#xff09;&#xff1a;XLOOKUP&#xff08;高级查找&#xff0c;较新版本Excel提供&#xff09;&#xff1a; 在Excel中&…

React第二十六章(createPortal)

createPortal 注意这是一个API&#xff0c;不是组件&#xff0c;他的作用是&#xff1a;将一个组件渲染到DOM的任意位置&#xff0c;跟Vue的Teleport组件类似。 用法 import { createPortal } from react-dom;const App () > {return createPortal(<div>小满zs<…