golang的timer一些坑
本文代码部分基于dive-to-gosync-workshop的代码
Golang 的NewTimer方法调用后,生成的timer会放入最小堆,一个后台goroutine会扫描这个堆,将到时的timer进行回调和channel(下面代码的 c := make(chan Time,1) )写入
// 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
}
而golang的timer的Stop方法, 是只负责把timer从堆里移除,不负责close 上面的channel(为什么不close channel?目前看只是为了超时时, 底层代码处理简单不crash。其实golang官方是可以做到的超时时正确处理channel的),这样就买下了一些坑。
下面的代码示范了这些坑和处理方法,其中 wrongResetAfterFired(..) 说明了超时后的channel被写入,如果没有被主动的正确接收,会导致的reset后的timer依然从channel拿到上一次的通道数据。
而wrongStopMore(...) 说明,如果channel没有被写入,也不要直接去等待,会导致deadlock
package main import (
"fmt"
"log"
"time"
) // [jz] 关于timer一个比较重要的点是,newtimer后,timer会放入最小堆,然后有一个goroutine来扫描,到期的进行回调和channel写入
// stop只负责将timer从堆删除,不负责close channel
func main() {
log.Println("✔︎ resetBeforeFired")
resetBeforeFired()
fmt.Println() log.Println("✘ wrongResetAfterFired")
wrongResetAfterFired()
fmt.Println() log.Println("✔︎ correctResetAfterFired")
correctResetAfterFired()
fmt.Println() log.Println("✔︎ stop n times")
stopMore()
fmt.Println() log.Println("✘ stop n times but with drain")
wrongStopMore()
fmt.Println() log.Println("✘ too many receiving")
wrongReceiveMore()
} func resetBeforeFired() {
timer := time.NewTimer(5 * time.Second)
b := timer.Stop()
log.Printf("stop: %t", b)
timer.Reset(1 * time.Second)
t := <-timer.C
log.Printf("fired at %s", t.String())
} func wrongResetAfterFired() {
timer := time.NewTimer(5 * time.Millisecond)
time.Sleep(time.Second) // sleep 1s能保证上面的timer 超时,channel被写入 b := timer.Stop()
log.Printf("stop: %t", b)
tt := timer.Reset(10 * time.Second)
fmt.Println(tt)
// 此时拿到的是第一个timer(5毫秒那个)的timeout的channel值
t := <-timer.C
log.Printf("fired at %s", t.String())
} func correctResetAfterFired() {
timer := time.NewTimer(5 * time.Millisecond)
time.Sleep(time.Second) b := timer.Stop()
log.Printf("stop: %t", b)
// 如果stop的时候发现已经超时,此时要把channel里的写入读出,免得后面reset时读出之前的channel里的值
if !b {
t := <-timer.C
fmt.Println(t.String())
}
log.Printf("reset")
timer.Reset(10 * time.Second)
t := <-timer.C
log.Printf("fired at %s", t.String())
} func wrongReceiveMore() {
timer := time.NewTimer(5 * time.Millisecond)
t := <-timer.C
log.Printf("fired at %s", t.String()) t = <-timer.C
log.Printf("receive again at %s", t.String())
} func stopMore() {
timer := time.NewTimer(5 * time.Millisecond)
b := timer.Stop()
log.Printf("stop: %t", b)
time.Sleep(time.Second)
b = timer.Stop()
log.Printf("stop more: %t", b)
} /*
newtimer后,timer会放入最小堆,然后有一个goroutine来扫描,到期的进行回调和channel写入
stop只负责将timer从堆删除,不负责close channel
*/
func wrongStopMore() {
timer := time.NewTimer(5 * time.Millisecond)
b := timer.Stop()
log.Printf("stop: %t", b)
time.Sleep(time.Second)
b = timer.Stop()
if !b { // 可以考虑这样解决:if !b && len(timer.C) > 0
// 之所以出问题,是因为,第一次Stop调用,发生在timer超时前,此时timer已经从堆删除,而timer本身没有超时,所以不需要发送channel
// 此时你去等待timer.C是不会有结果的
// 比如你在第一个timer.Stop前sleep 1s,让timer超时,channel会被写入,此时等待timer .C就不会有问题
<-timer.C
}
time.Sleep(1 * time.Second)
log.Printf("stop more: %t", b)
}
golang的timer一些坑的更多相关文章
- Golang开发者常见的坑
Golang开发者常见的坑 目录 [−] 初级 开大括号不能放在单独的一行 未使用的变量 未使用的Imports 简式的变量声明仅可以在函数内部使用 使用简式声明重复声明变量 偶然的变量隐藏Accid ...
- go的变量redeclare的问题,golang的一个小坑
go的变量声明有几种方式: 1 通过关键字 var 进行声明 例如:var i int 然后进行赋值操作 i = 5 2 最简单的,通过符号 := 进行声明和赋值 例如: i:=5 golang会 ...
- golang协程踩坑记录
1.主线程等待多个协程执行完毕后,再执行下面的程序.golang提供了一个很好用的工具. sync.WaitGroup下面是个简单的例子. 执行结果: 2.主线程主动去结束已经启动了的多个协程.执行结 ...
- golang闭包里的坑
介绍 go的闭包是一个很有用的东西.但是如果你不了解闭包是如何工作的,那么他也会给你带来一堆的bug.这里我会拿出Go In Action这本书的一部分代码,来说一说在使用闭包的时候可能遇到的坑.全部 ...
- golang中的那些坑之迭代器中的指针使用
今天在编写代码的时候,遇到了一个莫名其妙的错误,debug了半天,发现这是一个非常典型且易犯的错误.记之 示例代码: package main import "fmt" type ...
- 一个有关Golang变量作用域的坑
转自:http://tonybai.com/2015/01/13/a-hole-about-variable-scope-in-golang/ 临近下班前编写和调试一段Golang代码,但运行结果始终 ...
- golang 中timer,ticker 的使用
写一个程序, 5s, 10s后能定时执行一个任务,同时能不停的处理来的消息. ------------------------------------------------------------- ...
- Golang写文件的坑
Golang写文件一般使用os.OpenFile返回文件指针的Write方法或者WriteString或者WriteAt方法,但是在使用这三个方法时候经常会遇到写入的内容和实际内容有出入,因为这几个函 ...
- golang的defer踩坑汇总
原文链接:http://www.zhoubotong.site/post/50.html defer语句用于延迟函数调用,每次会把一个函数压入栈中,函数返回前再把延迟的函数取出并执行.延迟函数可以有参 ...
随机推荐
- Navicat10.1.11使用记录
设计表的时候有个允许空值(null),如果不勾选,则无法插入null(但是可以插入‘null’),且默认值不能为null: 如果某个字段没有设置默认值,而插入时又没有给此字段赋值,则会提示warnin ...
- Java 集合的工具类Collections的常用方法
Collections类 java.utils.Collections是集合工具类,用来对集合进行操作. Collections类的常用方法 这里介绍四个常用方法: addAll(Collection ...
- PBFT && RBFT算法流程
PBFT && RBFT算法流程以及其实现(上) 这篇文章主要是讲一下RBFT中共识算法流程以及节点的加入的流程.在下一篇博客中,将使用Java实现该算法. 传统的PBFT算法无法动态 ...
- <密码学系列>—信息安全威胁
懒惰等于将一个人活埋.--泰勒 本文已经收录至我的GitHub,欢迎大家踊跃star 和 issues. https://github.com/midou-tech/articles 点关注,不迷路! ...
- 实战_Spring_Cloud
目录 前言 开发环境 源码地址 创建工程 服务注册中心(Eureka) Eureka Server Eureka Client 注册中心高可用 小结 负载均衡(Ribbon) RestTemplate ...
- 【python系统学习06】一张图看懂列表并学会操作
点击跳转-原文地址 数据类型 - 列表(list) 「目录:」 一张图了解列表 列表是什么 列表长啥样 语法格式 代码示例 格式特征 列表定义 列表操作 - 提取单个:偏移量 什么是偏移量 偏移量提取 ...
- C++模板编程与宏编程经验谈
C++模板编程与宏编程经验谈 有人说C 与C++的不同主要是因为C++支持模板,不要说区别是面向对象化编程,因为C同样能很好的实现对象化编程,面向对象化其实只是思想,在很多语言中都能实现,区别在于实现 ...
- Python-直接存储类实例作为序列的元素
如果我们需要存储的数据有很多属性,并且存储的数量很多,可选择定义一个类来表示数据类型,而类的实体作为单个的成员进行存储,这样做的好处是可以只存储一个容器,而不需要每次都存储大量的数据,并且可以限制对数 ...
- 玩转Django2.0---Django笔记建站基础三(编写URL规则)
第三章 编写URL规则 URL(Uniform Resource Locator,统一资源定位符)是对可以从互联网上得到的资源位置和访问方法简洁的表示,是互联网上标准资源的地址. 在App里由于Dja ...
- 个人第四次作业--Alpha项目测试
这个作业属于哪个课程 https://edu.cnblogs.com/campus/xnsy/GeographicInformationScience 这个作业要求在哪里 https://www.cn ...