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语句用于延迟函数调用,每次会把一个函数压入栈中,函数返回前再把延迟的函数取出并执行.延迟函数可以有参 ...
随机推荐
- 代码注释规范-IDEA 配置 Java 类方法注释模板
1. 引言 团队开发时,业务模块分配的越清晰,代码注释管理越完善,越有利于后面维护,后面再管理也方便不少.另外也起着"文字砖"的作用,你懂的.注释不需要很详细,把代码块方法 ...
- vue状态管理vuex从浅入深详细讲解
1.vuex简介以及创建一个简单的仓库 vuex是专门为vue框架而设计出的一个公共数据管理框架,任何组件都可以通过状态管理仓库数据沟通,也可以统一从仓库获取数据,在比较大型的应用中,数据交互庞大的情 ...
- Mybatis中jdbcType的类型
具体支持的类型参见:org.apache.ibatis.type.JdbcType ARRAY, BIT, TINYINT, SMALLINT, INTEGER, BIGINT, FLOAT, REA ...
- mysql复习1
SQL语句分为以下三种类型: DML: Data Manipulation Language 数据操纵语言,用于查询与修改数据记录,包括如下SQL语句:INSERT:添加数据到数据库中UPDATE:修 ...
- Xcode10:library not found for -lstdc++.6.0.9 临时解决
1.https://pan.baidu.com/s/1IkbZb6qaxgvghP1HEFQa6w?errno=0&errmsg=Auth%20Login%20Sucess&& ...
- springboot的yml不显示的原因
首先排除插件原因 1 安装好插件Ctrl+Alt+S 2 查看修改的application.yml是什么格 在yaml格式中添加*.yaml和*.yml 3 查看maven是否配置完善
- Oracle GoldenGate 12.3微服务架构指北
Microservices Architecture introduction Microservices Architecture is a method or approach to develo ...
- 将DataTable数据转换成List泛型数据
这里有一个实体类: public class Menuss { public int Id { get; set; } public string Te ...
- linux系统iot平台编程阶段总结
1.inline内联函数 在C语言中,如果一些函数被频繁调用,不断地有函数入栈,即函数栈,会造成栈空间或栈内存的大量消耗. 为了解决这个问题,特别的引入了inline修饰符,表示为内联函数. 在使用循 ...
- 大事务造成的延迟(从binlog入手分析)
log_event.cc 入口: int Query_log_event::do_apply_event(Relay_log_info const *rli,const char *query_arg ...