0. 前言

在进行并发编程时,有时候会需要定时功能,比如监控某个GO程是否会运行过长时间、定时打印日志等等。

GO标准库中的定时器主要有两种,一种为Timer定时器,一种为Ticker定时器。Timer计时器使用一次后,就失效了,需要Reset()才能再次生效。而Ticker计时器会一直生效,接下来分别对两种进行介绍。

1. Timer定时器

首先介绍一下GO定时器的实现原理
在一个GO进程中,其中的所有计时器都是由一个运行着 timerproc() 函数的 goroutine 来保护。它使用时间堆(最小堆)的算法来保护所有的 Timer,其底层的数据结构基于数组的最小堆,堆顶的元素是间隔超时最近的 Timer,这个 goroutine 会定期 wake up,读取堆顶的 Timer,执行对应的 f 函数或者 sendtime()函数(下文会对这两个函数进行介绍),而后将其从堆顶移除。

接着看看Timer的结构

type Timer struct {C <-chan Time// contains filtered or unexported fields
}

Timer中对外暴露的只有一个channel,这个 channel 也是定时器的核心。当计时结束时,Timer会发送值到channel中,外部环境在这个 channel 收到值的时候,就代表计时器超时了,可与select搭配执行一些超时逻辑。可以通过time.NewTimertime.AfterFunc或者 time.Afte对一个Timer进行创建。

1.1 time.NewTimer() 和 time.After()

1.1.1 time.NewTimer()

查看以下简单的应用代码:

package mainimport ("fmt""time"
)
type H struct {t *time.Timer
}func main()  {fmt.Println("main")h:=H{t: time.NewTimer(1*time.Second)}go h.timer()time.Sleep(10*time.Second)
}func (h *H) timer()  {for  {select {case <-h.t.C:fmt.Println("timer")}}}

我们创建了一个timer,设置时间为1S。然后使用一个select对timer的C进行接受,GO程运行timer()函数,会一直阻塞直到超时发生(接收到C的数据),此时打印timer。

Stop() 停止 Timer

func (t *Timer) Stop() bool

Stop() 是 Timer 的一个方法,调用 Stop()方法,会停止这个 Timer 的计时,使其失效,之后触发定时事件。

实际上,调用此方法后,此Timer会被从时间堆中移除。

Reset()重置Timer

注意,Timer定时器超时一次后就不会再次运行,所以需要调用Reset函数进行重置。修改select中代码,在Case中添加一个重置的代码:

select {
case <-h.t.C:fmt.Println("timer")h.t.Reset(1*time.Second)
}

可以看到,会不停的打印timer,这是因为使用了Reset函数重置定时器。

注意!不能随意的对Reset方法进行调用,官网文档中特意强调:

For a Timer created with NewTimer, Reset should be invoked only on stopped or expired timers with drained channels.

大概的意思就是说,除非Timer已经被停止或者超时了,否则不要调用Reset方法,因为,如果这个 Timer 还没超时,不先去Stop它,而是直接Reset,那么旧的 Timer 仍然存在,并且仍可能会触发,会产生一些意料之外的事。所以通常使用如下的代码,安全的重置一个不知状态的Timer(以上的代码中,Reset调用时,总是处于超时状态):

if !t.Stop() {select {case <-h.t.C: default:}
}
h.t.Reset(1*time.Second)

1.1.2 time.After()

此方法就像是一个极简版的Timer使用,调用time.After(),会直接返回一个channel,当超时后,此channel会接受到一个值,简单使用如下:

package mainimport ("fmt""time"
)func main() {fmt.Println("main")go ticker()time.Sleep(100 * time.Second)
}func ticker() {for {select {case <-time.After(1 * time.Second):fmt.Println("timer")}}
}

注意,此方法虽然简单,但是没有Reset方法来重置定时器,但是可以搭配for 和select的重复调用来模拟重置。

1.1.3 sendtime函数

NewTimerAfter这两种创建方法,会Timer在超时后,执行一个标准库中内置的函数:sendTime,来将当前的时间发送到channel中。

1.2 time.AfterFunc

此方法可以接受一个func类型参数,在计时结束后,会运行此函数,查看以下代码,猜猜会出现什么结果?

package mainimport ("fmt""time"
)func main() {fmt.Println("main")t := time.AfterFunc(1*time.Second, func() {fmt.Println("timer")})go timer(t)time.Sleep(10 * time.Second)
}
func timer(t *time.Timer) {select {case <-t.C:fmt.Println("123")}
}

结果只打印了main以及timer。这是因为此方法并不会调用上文提到的sendtime()函数,即不会发送值给Timer的Channel,所以select就会一直阻塞。

f函数

特意将AfterFunc和以上的NewTimerAfter,就是因为f函数的存在。这种方式创建的Timer,在到达超时时间后会在单独的goroutine里执行函数f,而不会执行sendtime函数。

注意,外部传入的f参数并非直接运行在timerproc中,而是启动了一个新的goroutine去执行此方法。

2. Ticker定时器

Ticker定时器可以周期性地不断地触发时间事件,不需要额外的Reset操作。

其使用方法与Timer大同小异。通过time.NewTicker对Ticker进行创建,简单的使用如下:

package mainimport ("fmt""time"
)func main() {fmt.Println("main")t:=time.NewTicker(1*time.Second)go timer(t)time.Sleep(10 * time.Second)
}
func timer(t *time.Ticker) {for{select {case <-t.C:fmt.Println("timer")}}
}

GO定时器原理及使用相关推荐

  1. 实现quartz定时器及quartz定时器原理介绍

    今天研究定时器,在网上看了一篇不错的文章,推荐给大家! 实现quartz定时器及quartz定时器原理介绍

  2. 闫刚 nuttx的posix的定时器原理

    文章目录 定时器工作原理 资源 定时器模块初始化 定时器创建 启动定时器 定时器超时函数 定时器工作原理 定时器主要是使用waitdog进行实现,周期处理是通过timer_timeout进行处理 资源 ...

  3. JavaScript定时器原理分析

    JavaScript中的定时器大家基本在平时的开发中都遇见过吧,但是又有多少人去深入的理解其中的原理呢?下面我们就来分析一下定时器的实现原理. 一.储备知识 在我们在项目中一般会遇见过这样的两种定时器 ...

  4. 操作系统定时器原理分析(基于linux0.11)

    操作系统的定时器原理是,操作系统维护了一个定时器节点的链表,新增一个定时器节点时,设置一个jiffies值,这是触发定时中断的频率.linux0.11版本里是1秒触发100次,即10毫秒一次.加入新增 ...

  5. 嵌入式单片机基础篇(十)之Systick定时器原理及应用

    Systick定时器原理及应用 1.什么是Systick定时器? 回答: (1)ystick定时器,是一个简单的定时器,对于CM3,CM4内核芯片,都有Systick定时器. (2)Systick定时 ...

  6. java定时器的原理_spring定时器原理是什么?

    作为程序员来说,我们可能经常会使用到spring框架去实现各种各样的需求开发,其中的一项,定时器也是经常会被使用到的,那么你是否清楚其原理是什么呢? 首先我们来看看简单定时器实现方法:用Schedul ...

  7. libev源码解析——定时器原理

    本文将回答<libev源码解析--I/O模型>中抛出的两个问题.(转载请指明出于breaksoftware的csdn博客) 对于问题1:为什么backend_poll函数需要指定超时?我们 ...

  8. JavaScript定时器原理及高级使用

    JavaScript里面内置了两个定时器,一个是setTimeout()一个是setInterval().下面将由浅入深来理解一下定时器的工作原理. 使用方式: setTimeout() setTim ...

  9. STM32G4之高级定时器原理

    文章目录 前言 一.基本介绍 二.输入捕获通道原理 1.普通输入捕获 2.PWM模式 三.输出比较通道原理 PWM 输出模式 总结 前言 提示:以下是本篇文章正文内容 一.基本介绍 高级定时器是TIM ...

最新文章

  1. 案例:Oracle dul数据挖掘 磁盘损坏dul提取数据文件中表的数据及l
  2. 自制纯css下拉导航
  3. html播放完视频自动关闭,html视频播放完后跳转
  4. Trie实现(C++)
  5. Node --- EventProxy的原理
  6. 招聘APP如何搭建信用体系以避免求职者被骗
  7. Java:实验四第5题
  8. 微波工程(第三版)中关于史密斯圆图部分总结
  9. pdf文档统计字数的问题
  10. C# 打开word文件
  11. H5页面调用扫一扫功能
  12. 阿里中台搞了3年,搞凉了?网传:副总裁玄难“背锅”,辞职创业!
  13. ODT,ZQ校准,OCT,TDQS
  14. 曾经排名第一的安全软件,为啥会变成流氓软件?
  15. 论文笔记:Show, Control and Tell:A Framework for Generating Controllable and Grounded Captions
  16. 全国计算机等级考试二级Java全真模拟
  17. 小鸟云独享虚拟主机和共享虚拟主机区别对比
  18. 野火STM32寄存器点亮LED灯详解
  19. 信息安全(二)——密码学
  20. 实现一个简易chat聊天室(4种方法)

热门文章

  1. 海康威视iVMS综合安防系统任意文件上传漏洞复现 (0day)
  2. python 多维数组去重_python多维数组运算-女性时尚流行美容健康娱乐mv-ida网
  3. 元宇宙遇到中国年,Wearable Dao“中国红”主题VOX作品征集
  4. tui-editor(富文本编辑器插件)安装报错处理方法
  5. pku1322 Chocolate
  6. 12306半自动抢票
  7. r76800h和r76850hs区别
  8. user-cf的理解-初衷
  9. Oracle回收站空间使用与清理
  10. Pytest.fixture