Go语言实现定时器的原理及使用详解

目录
  • 0. 前言
  • 1. Timer定时器
    • 1.1 time.NewTimer() 和 time.After()
    • 1.2 time.AfterFunc
  • 2. Ticker定时器

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.NewTimer、time.AfterFunc或者 time.Afte对一个Timer进行创建。

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

1.1.1 time.NewTimer()

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

package main

import (
   "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 main

import (
	"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函数

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

1.2 time.AfterFunc

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

package main

import (
   "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和以上的NewTimer和After,就是因为f函数的存在。这种方式创建的Timer,在到达超时时间后会在单独的goroutine里执行函数f,而不会执行sendtime函数。

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

2. Ticker定时器

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

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

package main

import (
   "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语言实现定时器的原理及使用详解的文章就介绍到这了,更多相关Go语言定时器内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Go Ticker 周期性定时器用法及实现原理详解

    目录 前言 Ticker 应用示例 创建定时器 停止定时器 实现原理 数据结构 runtimeTimer 创建Ticker 停止Ticker Ticker 与 Timer 区别 小结 前言 定时器在Go语言应用中使用非常广泛,Go语言的标准库里提供两种类型的计时器,一种是一次性的定时器Timer,另外一种是周期性的定时器Ticker.本文主要来看一下Ticker的用法和实现原理,需要的朋友可以参考以下内容,希望对大家有帮助. Ticker Ticker是周期性定时器,即周期性的触发一个事件,它会

  • Go定时器的三种实现方式示例详解

    目录 Sleep Timer Ticker 小结 Sleep 很多时候需要周期性的执行某些操作,就需要用到定时器.定时器有三种思路. 本节源码位置 github.com/golang-mini… 使用休眠,让当前Goroutine休眠一定的时间来实现定时的效果,缺点是程序执行速度不均匀,导致定时周期不均匀. for{ fmt.Println(time.Now()) time.Sleep(time.Second*1) } Timer Go 语言的内置包,指定一个时间开始计时,时间到之后会向外发送通

  • golang 一次性定时器Timer用法及实现原理详解

    目录 前言 Timer timer结构体 创建定时器 停止定时器 重置定时器 实现原理 数据结构 runtimeTimer 创建Timer 停止Timer 重置Timer 前言 定时器在Go语言应用中使用非常广泛,Go语言的标准库里提供两种类型的计时器,一种是一次性的定时器Timer,另外一种是周期性的定时器Ticker.本文主要来看一下Timer的用法和实现原理,需要的朋友可以参考以下内容,希望对大家有帮助. Timer Timer是一种单一事件的定时器,即经过指定的时间后触发一个事件,因为T

  • Go编写定时器与定时任务详解(附第三方库gocron用法)

    目录 Go 编写定时器和定时任务 Timer Ticker gocron 安装 Demo 总结 Go 编写定时器和定时任务 在 项目开发当中,可能会遇到这样的场景: 1 A任务需要在多久之后执行一次(定时器) 2.B任务需要每隔多长时间执行一次 (定时任务) 谈到定时任务,离不开linux的crontab. 先简单了解下crontab // 每隔1秒执行一次 */1 * * * * ? // 每隔1分钟执行一次 0 */1 * * * ? // 每天0点执行一次 0 0 0 * * ? // 每

  • go语言定时器Timer及Ticker的功能使用示例详解

    目录 定时器1-"*/5 * * * * *" 设置说明 定时器2-Timer-Ticker Timer-只执行一次 Ticker-循环执行 Timer延时功能 停止和重置定时器 定时器Ticker使用 定时器1-"*/5 * * * * *" package main import ( "fmt" "github.com/robfig/cron" ) //主函数 func main() { cron2 := cron.New

  • 用golang实现一个定时器任务队列实例

    很有幸得到公司信任,采用新的语言进行一些底层服务的开发,在实现功能的同时,也获得了一些感悟,因此在这记录一下,方便自己查看也可以共享给大家. golang中定时器 golang中提供了2种定时器timer和ticker(如果JS很熟悉的话应该会很了解),分别是一次性定时器和重复任务定时器. 一般用法: func main() { input := make(chan interface{}) //producer - produce the messages go func() { for i

  • Go语言实现定时器的原理及使用详解

    目录 0. 前言 1. Timer定时器 1.1 time.NewTimer() 和 time.After() 1.2 time.AfterFunc 2. Ticker定时器 0. 前言 在进行并发编程时,有时候会需要定时功能,比如监控某个GO程是否会运行过长时间.定时打印日志等等. GO标准库中的定时器主要有两种,一种为Timer定时器,一种为Ticker定时器.Timer计时器使用一次后,就失效了,需要Reset()才能再次生效.而Ticker计时器会一直生效,接下来分别对两种进行介绍. 1

  • Go语言基础闭包的原理分析示例详解

    目录 一. 闭包概述 二. 代码演示 运行结果 代码说明 一. 闭包概述 闭包就是解决局部变量不能被外部访问的一种解决方案 闭包是把函数当作返回值的一种应用 二. 代码演示 总体思想为:在函数内部定义局部变量,把另一个函数当作返回值,局部变量对于返回值函数相当于全部变量,所以多次调用返回值函数局部变量的值跟随变化. // closure.go package main import ( "fmt" "strings" ) func main() { f := clo

  • Java语言class类用法及泛化(详解)

    这篇文章主要介绍了Java语言class类用法及泛化(详解),大家都知道Java程序在运行过程中,对所有的对象进行类型标识,也就是RTTI.这项信息记录了每个对象所属的类.虚拟机通常使用运行时类型信息选准正确方法去执行,用来保存这些类型信息的类是Class类.Class类封装一个对象和接口运行时的状态,当装载类时,Class类型的对象自动创建,具体内容介绍如下: 说白了就是: Class类也是类的一种,只是名字和class关键字高度相似.Java是大小写敏感的语言. Class类的对象内容是你创

  • Java语言实现快速幂取模算法详解

    快速幂取模算法的引入是从大数的小数取模的朴素算法的局限性所提出的,在朴素的方法中我们计算一个数比如5^1003%31是非常消耗我们的计算资源的,在整个计算过程中最麻烦的就是我们的5^1003这个过程 缺点1:在我们在之后计算指数的过程中,计算的数字不都拿得增大,非常的占用我们的计算资源(主要是时间,还有空间) 缺点2:我们计算的中间过程数字大的恐怖,我们现有的计算机是没有办法记录这么长的数据的,所以说我们必须要想一个更加高效的方法来解决这个问题 当我们计算AB%C的时候,最便捷的方法就是调用Ma

  • Python 异步协程函数原理及实例详解

    这篇文章主要介绍了Python 异步协程函数原理及实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一. asyncio 1.python3.4开始引入标准库之中,内置对异步io的支持 2.asyncio本身是一个消息循环 3.步骤: (1)创建消息循环 (2)把协程导入 (3)关闭 4.举例: import threading # 引入异步io包 import asyncio # 使用协程 @ asyncio.coroutine def

  • Java8默认方法Default Methods原理及实例详解

    这篇文章主要介绍了Java8默认方法Default Methods原理及实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 Java 8 引入了新的语言特性--默认方法(Default Methods). Default methods enable new functionality to be added to the interfaces of libraries and ensure binary compatibility wit

  • java中Servlet监听器的工作原理及示例详解

    监听器就是一个实现特定接口的普通java程序,这个程序专门用于监听另一个java对象的方法调用或属性改变,当被监听对象发生上述事件后,监听器某个方法将立即被执行. 监听器原理 监听原理 1.存在事件源 2.提供监听器 3.为事件源注册监听器 4.操作事件源,产生事件对象,将事件对象传递给监听器,并且执行监听器相应监听方法 监听器典型案例:监听window窗口的事件监听器 例如:swing开发首先制造Frame**窗体**,窗体本身也是一个显示空间,对窗体提供监听器,监听窗体方法调用或者属性改变:

  • C语言实现的一个三子棋游戏详解流程

    目录 前言 一.三子棋完成程序运行结果 二.三子棋代码实现 1.创建源文件与头文件 2.整体页面的制作 3.制作并打印棋盘 1.在test.c文件中,定义函数game(); 2.在game.h 头文件中 3.在game.c源文件中 4.人机互动下棋 1.在test.c源文件中 2.在game.h头文件中 3.在game.c源文件中 4.此时打印效果 5.判断输赢 1.在test.c源文件中 2.在game.h头文件中 3.在game.c源文件中 4.最终实现结果 最后 前言 三子棋是我们先前所学

  • C语言 小游戏打砖块实现流程详解

    始祖是美国英宝格公司(en:Atari Games,ja:アタリ (ゲーム))于1976年推出的街机游戏"Breakout"(en:Breakout),由该公司在1972年发行的"PONG"(en:PONG,ja:ポン (ゲーム),世界上第一款电子游戏,类似台球)改良而来.相较于其前作,一个人就可以玩与变化丰富这两项特点让Breakout相当卖座,使各家公司竞相模仿. 因为规则简单与游戏性,现在许多移动电话都有内建打砖块游戏,也有许多因特网小游戏版本,目前在网上可以

  • go语言context包功能及操作使用详解

    目录 Context包到底是干嘛用的? context原理 什么时候应该使用 Context? 如何创建 Context? 主协程通知有子协程,子协程又有多个子协程 context核心接口 emptyCtx结构体 Backgroud TODO valueCtx结构体 WithValue向context添加值 Value向context取值 示例 WithCancel可取消的context cancelCtx结构体 WithDeadline-超时取消context WithTimeout-超时取消

随机推荐