Go 语言一次性定时器使用方式和实现原理

简介: Go 语言一次性定时器使用方式和实现原理

介绍

在 Go 语言标准库 time 包中的 Timer 类型,它是表示单一事件的计时器,也就是说它是一次性定时器。

在 Go 语言项目开发中,定时器使用广泛,本文我们介绍 Go 语言中怎么使用 Timer,以及它的实现原理。

使用方式

使用 Timer 一次性定时器,需要导入 time 包,创建定时器的方式有两种,分别是:

func NewTimer(d Duration) *Timer

使用 func NewTimer 创建 Timer,入参是定时器的等待时间,时间到达时,发送当前时间到 channel中。

示例代码:

func main() {
 nowTime := time.Now().Unix()
 fmt.Println(nowTime)
 NewTimerDemo()
}
func NewTimerDemo() {
 timer := time.NewTimer(2 * time.Second)
 select {
 case <-timer.C:
  currentTime := time.Now().Unix()
  fmt.Println(currentTime, "do something")
 }
}

输出结果:

1665894136
1665894138 do something

通过阅读上面这段代码,我们可以发现我们定义了一个 2s 后执行的定时器 timer,然后使用 select 读取 timer.C 中的数据,当读取到数据时,执行特定业务逻辑代码。

func AfterFunc(d Duration, f func()) *Timer

使用 func AfterFunc 创建 Timer,入参是定时器等待时间,和时间到达时执行的函数。

示例代码:

func main() {
 nowTime := time.Now().Unix()
 fmt.Println(nowTime)
 AfterFuncDemo()
}
func AfterFuncDemo() {
 time.AfterFunc(2 * time.Second, func() {
  currentTime := time.Now().Unix()
  fmt.Println(currentTime, "do something")
 })
 time.Sleep(3 * time.Second)
}

阅读上面这段代码,细心的读者朋友们可能已经发现,我们在代码末尾使用 time.Sleep(),这是因为 time.AfterFunc() 是异步执行的,所以需要等待协成退出。

03

实现原理

我们在源码中查看 Timer 的数据结构,发现它包含两个字段,其中一个是可导出字段 C,这是一个 Time类型的 chan;另一个是不可导出字段 r,这是一个 runtimeTimer 类型。

type Timer struct {
    C <-chan Time
    r runtimeTimer
}

实际上,每个 Go 应用程序底层都会有一个特定的协程管理 Timer,该协程(底层协程)监控到某个 Timer 指定的时间到达时,就会将当前时间发送到 C 中,然后上层读取到 C 中的数据时,执行相关业务逻辑代码。

底层协程监控 Timerr 字段中的数据,我们在源码中查看一下 runtimeTimer 的数据结构:

type runtimeTimer struct {
    tb     uintptr
    i      int
    when   int64
    period int64
    f      func(interface{}, uintptr)
    arg    interface{}
    seq    uintptr
}

阅读上面这段代码,我们可以发现 runtimeTimer 中包含的所有字段,我们重点了解 whenfarg

  • when:定时器执行时间。
  • f:定时器执行的回调函数。
  • arg:定时器执行的回调函数的参数。

在简单了解 Timer 的数据结构之后,我们在源码中查看一下 func NewTimer 的代码:

// 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
}

阅读上面这段代码,我们可以发现 func NewTimer 的实现非常简单,它实际上就是构造了一个 Timer,然后把 Timer.r 传参给 startTimer(),除了 startTimer() 函数外,还有两个函数,分别是 when()sendTime,其中 when() 是计算计时器的执行时间,sendTime 是计时器时间到达时执行的事件(实际上就是将当前时间写入通道中)。

sendTime 源码:

func sendTime(c interface{}, seq uintptr) {
 // Non-blocking send of time on c.
 // Used in NewTimer, it cannot block anyway (buffer).
 // Used in NewTicker, dropping sends on the floor is
 // the desired behavior when the reader gets behind,
 // because the sends are periodic.
 select {
 case c.(chan Time) <- Now():
 default:
 }
}

我们已经了解到,func NewTimer 将构造的 Timer.r 传参给 startTimer(),它负责把 runtimeTimer写入底层协程的数组中(如果底层协程未运行,它将会启动底层协程),将 Timer给底层协程监控,也就是上面讲到的,当底层协程监控到某个 Timer 指定时间到达时,将当前时间发送到它的通道中。

// startTimer adds t to the timer heap.
//go:linkname startTimer time.startTimer
func startTimer(t *timer) {
 if raceenabled {
  racerelease(unsafe.Pointer(t))
 }
 addtimer(t)
}

04

总结

本文我们介绍 Go 语言标准库 time 包提供的一次性定时器 Timer,不仅介绍了它的使用方式,还介绍了它的实现原理。

限于篇幅,本文没有介绍 Stop()Reset() 方法,感兴趣的读者朋友们可以查阅相关资料。

推荐阅读:

参考资料:

  1. https://pkg.go.dev/time#Timer
  2. https://gobyexample.com/timers


目录
相关文章
|
6天前
|
存储 JSON 监控
Viper,一个Go语言配置管理神器!
Viper 是一个功能强大的 Go 语言配置管理库,支持从多种来源读取配置,包括文件、环境变量、远程配置中心等。本文详细介绍了 Viper 的核心特性和使用方法,包括从本地 YAML 文件和 Consul 远程配置中心读取配置的示例。Viper 的多来源配置、动态配置和轻松集成特性使其成为管理复杂应用配置的理想选择。
23 2
|
4天前
|
Go 索引
go语言中的循环语句
【11月更文挑战第4天】
13 2
|
4天前
|
Go C++
go语言中的条件语句
【11月更文挑战第4天】
15 2
|
8天前
|
程序员 Go
go语言中的控制结构
【11月更文挑战第3天】
84 58
|
7天前
|
监控 Go API
Go语言在微服务架构中的应用实践
在微服务架构的浪潮中,Go语言以其简洁、高效和并发处理能力脱颖而出,成为构建微服务的理想选择。本文将探讨Go语言在微服务架构中的应用实践,包括Go语言的特性如何适应微服务架构的需求,以及在实际开发中如何利用Go语言的特性来提高服务的性能和可维护性。我们将通过一个具体的案例分析,展示Go语言在微服务开发中的优势,并讨论在实际应用中可能遇到的挑战和解决方案。
|
4天前
|
Go
go语言中的 跳转语句
【11月更文挑战第4天】
12 4
|
4天前
|
JSON 安全 Go
Go语言中使用JWT鉴权、Token刷新完整示例,拿去直接用!
本文介绍了如何在 Go 语言中使用 Gin 框架实现 JWT 用户认证和安全保护。JWT(JSON Web Token)是一种轻量、高效的认证与授权解决方案,特别适合微服务架构。文章详细讲解了 JWT 的基本概念、结构以及如何在 Gin 中生成、解析和刷新 JWT。通过示例代码,展示了如何在实际项目中应用 JWT,确保用户身份验证和数据安全。完整代码可在 GitHub 仓库中查看。
17 1
|
8天前
|
Go 数据处理 API
Go语言在微服务架构中的应用与优势
本文摘要采用问答形式,以期提供更直接的信息获取方式。 Q1: 为什么选择Go语言进行微服务开发? A1: Go语言的并发模型、简洁的语法和高效的编译速度使其成为微服务架构的理想选择。 Q2: Go语言在微服务架构中有哪些优势? A2: 主要优势包括高性能、高并发处理能力、简洁的代码和强大的标准库。 Q3: 文章将如何展示Go语言在微服务中的应用? A3: 通过对比其他语言和展示Go语言在实际项目中的应用案例,来说明其在微服务架构中的优势。
|
8天前
|
Go 数据处理 调度
探索Go语言的并发模型:Goroutines与Channels的协同工作
在现代编程语言中,Go语言以其独特的并发模型脱颖而出。本文将深入探讨Go语言中的Goroutines和Channels,这两种机制如何协同工作以实现高效的并发处理。我们将通过实际代码示例,展示如何在Go程序中创建和管理Goroutines,以及如何使用Channels进行Goroutines之间的通信。此外,本文还将讨论在使用这些并发工具时可能遇到的常见问题及其解决方案,旨在为Go语言开发者提供一个全面的并发编程指南。
|
6天前
|
Go 调度 开发者
探索Go语言中的并发模式:goroutine与channel
在本文中,我们将深入探讨Go语言中的核心并发特性——goroutine和channel。不同于传统的并发模型,Go语言的并发机制以其简洁性和高效性著称。本文将通过实际代码示例,展示如何利用goroutine实现轻量级的并发执行,以及如何通过channel安全地在goroutine之间传递数据。摘要部分将概述这些概念,并提示读者本文将提供哪些具体的技术洞见。