本文代码部分基于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一些坑的更多相关文章

  1. Golang开发者常见的坑

    Golang开发者常见的坑 目录 [−] 初级 开大括号不能放在单独的一行 未使用的变量 未使用的Imports 简式的变量声明仅可以在函数内部使用 使用简式声明重复声明变量 偶然的变量隐藏Accid ...

  2. go的变量redeclare的问题,golang的一个小坑

    go的变量声明有几种方式: 1 通过关键字 var 进行声明 例如:var i int   然后进行赋值操作 i = 5 2 最简单的,通过符号 := 进行声明和赋值 例如: i:=5 golang会 ...

  3. golang协程踩坑记录

    1.主线程等待多个协程执行完毕后,再执行下面的程序.golang提供了一个很好用的工具. sync.WaitGroup下面是个简单的例子. 执行结果: 2.主线程主动去结束已经启动了的多个协程.执行结 ...

  4. golang闭包里的坑

    介绍 go的闭包是一个很有用的东西.但是如果你不了解闭包是如何工作的,那么他也会给你带来一堆的bug.这里我会拿出Go In Action这本书的一部分代码,来说一说在使用闭包的时候可能遇到的坑.全部 ...

  5. golang中的那些坑之迭代器中的指针使用

    今天在编写代码的时候,遇到了一个莫名其妙的错误,debug了半天,发现这是一个非常典型且易犯的错误.记之 示例代码: package main import "fmt" type ...

  6. 一个有关Golang变量作用域的坑

    转自:http://tonybai.com/2015/01/13/a-hole-about-variable-scope-in-golang/ 临近下班前编写和调试一段Golang代码,但运行结果始终 ...

  7. golang 中timer,ticker 的使用

    写一个程序, 5s, 10s后能定时执行一个任务,同时能不停的处理来的消息. ------------------------------------------------------------- ...

  8. Golang写文件的坑

    Golang写文件一般使用os.OpenFile返回文件指针的Write方法或者WriteString或者WriteAt方法,但是在使用这三个方法时候经常会遇到写入的内容和实际内容有出入,因为这几个函 ...

  9. golang的defer踩坑汇总

    原文链接:http://www.zhoubotong.site/post/50.html defer语句用于延迟函数调用,每次会把一个函数压入栈中,函数返回前再把延迟的函数取出并执行.延迟函数可以有参 ...

随机推荐

  1. TensorFlow——tf.contrib.layers库中的相关API

    在TensorFlow中封装好了一个高级库,tf.contrib.layers库封装了很多的函数,使用这个高级库来开发将会提高效率,卷积函数使用tf.contrib.layers.conv2d,池化函 ...

  2. SpringSecurity 初始化流程源码

    SpringSecurity 初始化流程源码 本篇主要讲解 SpringSecurity初始化流程的源码部分,包括核心的 springSecurityFilterChain 是如何创建的,以及在介绍哪 ...

  3. mysql 向字段添加数据或者删除数据

    UPDATE table SET cids = CONCAT(cids , ',12') where id=id //向字段添加数据 //因为要用逗号分隔 所以在在前面加了一个逗号 UPDATE ta ...

  4. 【智能合约】编写复杂业务场景下的智能合约——可升级的智能合约设计模式(附Demo)

    智能合约的现状 以太坊在区块链上实现了智能合约的概念,用于:同质化通证发行(ERC-20).众筹.投票.存证取证等等,共同点是:合约逻辑简单,只是业务流程中的关键节点,而非整个业务流程.而智能合约想解 ...

  5. V模型

    V模型是Kevin Forsberg & Harold Mooz在1978年提出的,V模型强调测试在系统工程各个阶段中的作用,并将系统分解和系统集成的过程通过测试彼此关联.V模型从整体上看起来 ...

  6. TCP客户端服务器编程模型

    1.客户端调用序列 客户端编程序列如下: 调用socket函数创建套接字 调用connect连接服务器端 调用I/O函数(read/write)与服务器端通讯 调用close关闭套接字 2.服务器端调 ...

  7. 深入理解大数据之——事务及其ACID特性

    目录 事务简介 事物的定义 事务的目的 事务的状态 事务的ACID属性 ACID简介 原子性(Atomicity) 一致性(Consistency) 隔离性(Isolation) 持久性(Durabi ...

  8. BZOJ 3691 游行

    题目传送门 分析: 没被访问的点要C费用,跑一次路要C费用 把这两个统一一下试试... 那就是每次不标记起点或者终点 那就是路径覆盖了2333 二分图,x 部 i 号点与 y 部 j 号点连 i 到 ...

  9. Java学习笔记(二) 面向对象---构造函数

    面向对象---构造函数 特点 函数名与类名相同 不用定义返回值类型 不写return语句 作用 对象一建立,就对象进行初始化. 具体使用情况 class Student { Student(){ Sy ...

  10. sas9.2 windows7系统 10年11月后 建立永久数据集时,提示:“用户没有与逻辑库相应的授权级别

    先把你这个逻辑库删掉,在桌面创立空的新文件夹,然后用这个新文件夹在sas里新建逻辑库,名字照旧,再把你要的数据放进空文件夹就好了