作为一只在9127工作制下摸鱼的程序猿,周六自然是愉快的加班了。一早上除了一位新同学在我们的“敏捷迭代”下错删了接口之外没什么大事。

临近中午,突然隔壁组大佬找到我,表示有个go语言服务偶现panic的问题需要求助。了解了一下,原来是他们组的一个妹子(小姐姐??)写的代码的问题。okok,既然大佬都来找我了,帮忙解决下顺便……再好不过了。

咳咳,进入正题,将问题场景的代码先放出来:

 var flag booltimer1 := time.NewTicker(time.Millisecond * 500)timer2 := time.NewTicker(time.Millisecond * 1000)if flag {timer1.Stop()} else {timer2.Stop()}for {select {case <-timer1.C://todo do somethingcase <-timer2.C://todo do something else}}

其实要实现的功能很简单,两个定时器timer1和timer2,根据flag条件的不同,停止其中一个定时器,后续业务流程只有一个定时器生效。当然,这个功能很简单,有更好的写法,先不说代码的好坏,这段代码中隐藏的问题其实很容易忽略。

这段代码有一定几率引起select的两个分支都会进入的情况,与预期不符合,导致执行错误分支代码时,出现不可预期的问题。

先看下Ticker的结构和NewTicker方法的源码:

Ticker:

type Ticker struct {C <-chan Time // The channel on which the ticks are delivered.r runtimeTimer
}

NewTicker方法:

func NewTicker(d Duration) *Ticker {if d <= 0 {panic(errors.New("non-positive interval for NewTicker"))}// Give the channel a 1-element time buffer.// If the client falls behind while reading, we drop ticks// on the floor until the client catches up.c := make(chan Time, 1)t := &Ticker{C: c,r: runtimeTimer{when:   when(d),period: int64(d),f:      sendTime,arg:    c,},}startTimer(&t.r)return t
}

可以看到,Ticker其实是对runtimeTimer的一个封装,增加一个成员C,用作定时器超时触发的通道。而NewTicker就是对Ticker的创建过程,新建了通道C,并构造了runtimeTimer的结构,其中成员方法f就是runtimeTimer超时触发的方法,先不考虑runtimeTimer内部的实现,看一下sendTime方法:

func sendTime(c interface{}, seq uintptr) {// Non-blocking send of time on c.// Used in NewTimer, it cannot block anyway (buffer).// Used in NewTicker, dropping sends on the floor is// the desired behavior when the reader gets behind,// because the sends are periodic.select {case c.(chan Time) <- Now():default:}
}

不得不说,这里的设计还是很精妙的,c是一个缓冲空间为1的通道,使用缓冲通道的特性,做到了定时器外部业务不阻塞内部调度的特性。当定时器超时时,会将当前时间放入通道中,如果通道已经满了,不能放入就丢弃。总之,不会阻塞定时器的内部调度。而外部在使用时,只要从Ticker.C这个通道中,不断读取即可,能取到值时,说明发生过超时,执行相应业务即可。

另外问题代码中还调用了Stop方法,Stop方法内部实现主要是调用runtimeTimer的方法来停止,停止之后,定时器超时不会再触发上述的sendTimer方法,即不会再向通道c中放入数据使外部使用者读到。

简单明确了Ticker的实现,现在回到问题代码,看下究竟有什么问题。一开始在创建timer1和timer2是调用了NewTicker方法,根据上面的分析,调用了这两个方法之后,底层的定时器已经开始计时,接下来执行if条件判断后才停止了其中一个定时器。一般来讲,这种相邻几行代码之间,应该间隔时间很短,都在纳秒级别,即还没等到定时器触发,就停止了定时器。但是,go语言的协程调度的机制其实无法保证这种时间间隔。当发生方法调用时,当前协程是有可能出让出所占有的线程,让其它协程先跑的。以此来对外呈现出并发的效果,协程之间并不能完全保证并行。关于go的协程调度机制,后续可以再详细聊一聊。

这样一来,如果某些时候,正好这个执行这个方法的协程在这两行中间出让了线程,就极可能导致两行代码之间的时间间隔超过超时时间。一旦发生这种情况,虽然按逻辑停掉了一个定时器,但是在停掉之前已经触发了一次。这样Ticker.C这个通道里已经被放入了触发数据,继续往下执行select时,自然两个分支都会进入,从而引发了预期之外的错误。

这种问题现在再回头来看,可能会觉得理所当然不该这么用,但是如果不了解定时器的实现,贸然使用时,很难发现这个问题点。

记录下来,防止后面再踩坑。至于要怎么修改,其实看一眼要实现的目的很容易想到,就不在此赘述了。

winform 让他间隔一段时间 执行事件 且只执行一次_记一次golang定时器引发的诡异错误...相关推荐

  1. winform 让他间隔一段时间 执行事件 且只执行一次_Redis 事件机制详解

    点击上方"程序员历小冰",选择"置顶或者星标" 你的关注意义重大! Redis 采用事件驱动机制来处理大量的网络IO.它并没有使用 libevent 或者 li ...

  2. winform 让他间隔一段时间 执行事件 且只执行一次_米家智能插座改造:热水地暖(电热执行器、电磁阀类系统通用)接入米家...

    本文主要举例讲解将热水地暖改造后接入米家智能家庭. * 本文实际上在2020年1月底春节休假隔离时就已经编写完成,但因为小米蓝牙类温湿度计的"且"联动有bug,多个温湿度计的联合条 ...

  3. php 间隔时间执行任务,PHP间隔一段时间执行代码的方法

    PHP间隔一段时间执行代码的方法 发布于 2015-11-16 18:26:46 | 230 次阅读 | 评论: 0 | 来源: 网友投递 PHP开源脚本语言PHP(外文名: Hypertext Pr ...

  4. js中间隔一段时间执行

    js中间隔一段时间执行 var arr = [1, 2, 3, 4, 5] var i = 0 // 在外面定义一个变量作为判断的标准 var timer = setInterval(function ...

  5. 间隔一段时间重复自动登录网站、定时自动登录网站的软件 —— 定时执行专家,无需复杂编程,简单配置即可使用

    常常有网友在网上发帖求助,想找一个定时自动登录网页(网站)的软件,大致需求如下: 1.打开网页 → 2.填写用户名.密码 → 3.点击登录按钮 → 4.登录成功 另外,还有诸如:要求间隔是每3个小时自 ...

  6. java一段时间后执行一块代码_java自带的ScheduledExecutorService定时任务正常执行一段时间后部分任务不执行...

    目前我有用java自带的ScheduledExecutorService线程任务做定时调度功能,我开启了足够多的线程数,比如开了300,实际上用到的只有50个. 我用的是newScheduledThr ...

  7. java ThreadPool 执行一段时间后卡死,不再执行任务

    原文地址:http://www.5ixiudou.com/portal/detailInfo/1000000005/290 线程池实际使用过程中遇到的问题 运政新库:程序执行一段时间后,就卡住了,不执 ...

  8. Windows定时任务 每隔一段时间(最小到秒级)执行一次指定的脚本

    1.首先打开我们创建好的任务计划,比如我创建了一个任务计划叫做"守护进程" 如果不小心关掉了任务计划,可以在这里面重新打开:在控制面板 -> 管理工具(查看方式设置为小图标) ...

  9. 如何让便签的提醒事项间隔一段时间再次提醒

    便签已经是职场人士必备的记事工具,有便签帮助记事,可以给自己的大脑省些空间,把那些琐碎的事情,都记录在便签上,并设置好提醒时间.到了指定时间,提醒自己做某件事.那么有没有一种便签,可以让已经设置的提醒 ...

最新文章

  1. jdk动态代理实例和cglib动态代理实例_CGLib 动态代理 原理解析
  2. UICollectionView框架总结
  3. mapreduce编程实例(3)-求平均值
  4. Java消息系统简单设计与实现
  5. react native 原生模块桥接的简单说明
  6. Docker的四种网络模式和相关网络命令等操作
  7. Rabbitmq专题:rabbitMQ如何保证消息的可靠性投递?如何防止消息丢失
  8. 14.程序员的自我修养---附录
  9. 部门管理系统_维修工单管理系统的功能介绍
  10. 《机器视觉算法与应用》第3章 机器视觉算法之形态学——学习笔记
  11. 计算机应用基础146jpg,计算机应用基础——复习题2.pdf
  12. 计算机网络实验指导书 pdf,计算机网络实验指导书(新版).pdf
  13. sql注入检测工具 mysql_Java自动化SQL注入测试工具—jSQL Injection v0.5
  14. ipv6协议学习笔记(1)-ipv6基础
  15. 关于使用TP-Link桥接小米路由器
  16. 关于input在苹果和安卓手机上调用相机和相册的问题
  17. 用函数求出平均分、最高分和最低分
  18. 关于c#的书籍下载网站和地址
  19. 自动洗车APP开发需要满足用户哪些需求
  20. 海量存储检索原理系列文章(都是精华)

热门文章

  1. 卷起来了,写了一套Tensorflow和Pytorch的学习笔记(20G/代码/PPT/视频)
  2. BERT源码分析PART I
  3. 小程序 实名信息_微信小程序+商城信息管理系统
  4. 空间留言软件_四款高质量高性能优质软件,强大还免费,建议偷偷收藏使用
  5. 两台主机经过路由器传送数据的网络原理
  6. 操作系统——Linux 虚拟内存和物理内存的理解
  7. 吴恩达机器学习(六)神经网络(前向传播)
  8. 解决 LaTeX Error: File `.sty‘ not found.,直接进行本地导入就好了,暂时解决而已
  9. osg+ActiveQT嵌入ie64位
  10. 性能测试:基础(5)