Golang定时器Timer与Ticker的使用详解

 更新时间:2023年05月16日 17:02:12   作者:人艰不拆_zmc  
在 Go 里有很多种定时器的使用方法,像常规的 Timer、Ticker 对象,本文主要为大家介绍了Timer与Ticker的使用,感兴趣的小伙伴可以了解一下

1、概述

在 Go 里有很多种定时器的使用方法,像常规的 Timer、Ticker 对象,以及经常会看到的 time.After(d Duration) 和 time.Sleep(d Duration) 方法。以上这些定时器的使用方法都来自Golang 原生 time 包,使用time包可以用来执行一些定时任务或者是周期性的任务。

2、定时器使用

2.1 Timer 相关

func NewTimer(d Duration) *Timer
func (t *Timer) Reset(d Duration) bool
func (t *Timer) Stop() bool
func After(d Duration) <-chan Time
func AfterFunc(d Duration, f func()) *Timer
 
//timer例子
func main() {
   timer := time.NewTimer(3 * time.Second)  //启动定时器,生产一个Timer对象
   select {
   case <-timer.C:
      fmt.Println("3秒执行任务")
   }
   timer.Stop() // 不再使用了,结束它
}
 
//time.After例子
func main() {
   tChannel := time.After(3 * time.Second) // 其内部其实是生成了一个Timer对象
   select {
   case <-tChannel:
      fmt.Println("3秒执行任务")
   }
}
 
func main() {
  timer := time.NewTimer(3 * time.Second)
  for {
    timer.Reset(4 * time.Second) // 这样来复用 timer 和修改执行时间
    select {
    case <-timer.C:
      fmt.Println("每隔4秒执行任务")
    }
  }
}

从上面可以看出来 Timer 允许再次被启用,而 time.After 返回的是一个 channel,将不可复用。

而且需要注意的是 time.After 本质上是创建了一个新的 Timer 结构体,只不过暴露出去的是结构体里的 channel 字段而已。

因此如果在 for{...}里循环使用了 time.After,将会不断的创建 Timer。如下的使用方法就会带来性能问题:

错误使用:

for 里的 time.After 将会不断的创建 Timer 对象,虽然最终会回收,但是会造成无意义的cpu资源消耗

func main() {
   for {
      select {
      case <-time.After(3 * time.Second):
         fmt.Println("每隔3秒执行一次")
      }
   }
}

正确使用:

func main() {
   timer := time.NewTimer(3 * time.Second)
   for {
      timer.Reset(3 * time.Second) // 这里复用了 timer
      select {
      case <-timer.C:
         fmt.Println("每隔3秒执行一次")
      }
   }
}

2.2 Ticker 相关

这里的 Ticker 跟 Timer 的不同之处,就在于 Ticker 时间达到后不需要人为调用 Reset 方法,会自动续期。

func NewTicker(d Duration) *Ticker
func Tick(d Duration) <-chan Time
func (t *Ticker) Stop()
func main() {
  ticker := time.NewTicker(3 * time.Second)
  for range ticker.C {
    fmt.Print("每隔3秒执行任务")
  }
  ticker.Stop()
}

错误使用:

func main() {
   for {
      select {
      case <-time.Tick(3 * time.Second): // 这里会不断生成 ticker,而且 ticker 会进行重新调度,造成泄漏
         fmt.Println("每隔3秒执行一次")
      }
   }
} 

3、定时器使用示例

3.1 Ticker定时器

package main
import (
    "fmt"
    "time"
)
func main() {
    // Ticker 包含一个通道字段C,每隔时间段 d 就向该通道发送当时系统时间。
    // 它会调整时间间隔或者丢弃 tick 信息以适应反应慢的接收者。
    // 如果d <= 0会触发panic。关闭该 Ticker 可以释放相关资源。
    ticker1 := time.NewTicker(5 * time.Second)
    // 一定要调用Stop(),回收资源
    defer ticker1.Stop()
    go func(t *time.Ticker) {
        for {
            // 每5秒中从chan t.C 中读取一次
            <-t.C
            fmt.Println("Ticker:", time.Now().Format("2006-01-02 15:04:05"))
        }
    }(ticker1)
    time.Sleep(30 * time.Second)
    fmt.Println("ok")
}  

执行结果:

Ticker: 2022-01-18 13:39:30
Ticker: 2022-01-18 13:39:35
Ticker: 2022-01-18 13:39:40
Ticker: 2022-01-18 13:39:45
Ticker: 2022-01-18 13:39:50
ok
Ticker: 2022-01-18 13:39:55

可以看到每次执行的时间间隔都是一样的,由于main线程结束导致程序结束。

3.2 Timer定时器

package main
 
import (
    "fmt"
    "time"
)
 
func main() {
 
    // NewTimer 创建一个 Timer,它会在最少过去时间段 d 后到期,向其自身的 C 字段发送当时的时间
    timer1 := time.NewTimer(5 * time.Second)
 
    fmt.Println("开始时间:", time.Now().Format("2006-01-02 15:04:05"))
    go func(t *time.Timer) {
        times := 0
        for {
            <-t.C
            fmt.Println("timer", time.Now().Format("2006-01-02 15:04:05"))
 
            // 从t.C中获取数据,此时time.Timer定时器结束。如果想再次调用定时器,只能通过调用 Reset() 函数来执行
            // Reset 使 t 重新开始计时,(本方法返回后再)等待时间段 d 过去后到期。
            // 如果调用时 t 还在等待中会返回真;如果 t已经到期或者被停止了会返回假。
            times++
            // 调用 reset 重发数据到chan C
            fmt.Println("调用 reset 重新设置一次timer定时器,并将时间修改为2秒")
            t.Reset(2 * time.Second)
            if times > 3 {
                fmt.Println("调用 stop 停止定时器")
                t.Stop()
            }
        }
    }(timer1)
 
    time.Sleep(30 * time.Second)
    fmt.Println("结束时间:", time.Now().Format("2006-01-02 15:04:05"))
    fmt.Println("ok")
}

执行结果:

开始时间: 2022-01-18 13:25:43
timer 2022-01-18 13:25:48
调用 reset 重新设置一次timer定时器,并将时间修改为2秒
timer 2022-01-18 13:25:50
调用 reset 重新设置一次timer定时器,并将时间修改为2秒
timer 2022-01-18 13:25:52
调用 reset 重新设置一次timer定时器,并将时间修改为2秒
timer 2022-01-18 13:25:54
调用 reset 重新设置一次timer定时器,并将时间修改为2秒
调用 stop 停止定时器
结束时间: 2022-01-18 13:26:13
ok

可以看到,第一次执行时间为5秒以后。然后通过调用 time.Reset() 方法再次激活定时器,定时时间为2秒,最后通过调用 time.Stop() 把前面的定时器取消掉。

4、总结

ticker定时器表示每隔一段时间就执行一次,一般可执行多次。

timer定时器表示在一段时间后执行,默认情况下只执行一次,如果想再次执行的话,每次都需要调用 time.Reset() 方法,此时效果类似ticker定时器。同时也可以调用 Stop() 方法取消定时器

timer定时器比ticker定时器多一个 Reset() 方法,两者都有 Stop() 方法,表示停止定时器,底层都调用了stopTimer() 函数。

除了上面的定时器外,Go 里的 time.Sleep 也起到了类似一次性使用的定时功能。只不过 time.Sleep 使用了系统调用。而像上面的定时器更多的是靠 Go 的调度行为来实现。

无论哪种计时器,.C 都是一个 chan Time 类型且容量为 1 的单向 Channel,当有超过 1 个数据的时候便会被阻塞,以此保证不会被触发多次。

到此这篇关于Golang定时器Timer与Ticker的使用详解的文章就介绍到这了,更多相关Golang定时器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • golang 语言中错误处理机制

    golang 语言中错误处理机制

    Golang 的错误处理方式可能和这些你熟悉的语言有所不同,今天通过本文给大家分享golang 语言中错误处理机制,感兴趣的朋友一起看看吧
    2021-08-08
  • Go语言通过chan进行数据传递的方法详解

    Go语言通过chan进行数据传递的方法详解

    这篇文章主要为大家详细介绍了Go语言如何通过chan进行数据传递的功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起了解一下
    2023-06-06
  • Go学习笔记之map的声明和初始化

    Go学习笔记之map的声明和初始化

    map底层是由哈希表实现的,Go使用链地址法来解决键冲突,下面这篇文章主要给大家介绍了关于Go学习笔记之map的声明和初始化的相关资料,需要的朋友可以参考下
    2022-11-11
  • CentOS7使用yum安装Golang的超详细步骤

    CentOS7使用yum安装Golang的超详细步骤

    CentOS默认并没有安装golang运行环境,下面这篇文章主要给大家介绍了关于CentOS7使用yum安装Golang的超详细步骤,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-02-02
  • Go语言并发模型的2种编程方案

    Go语言并发模型的2种编程方案

    这篇文章主要介绍了Go语言并发模型的2种编程方案,本文给出共享内存和通过通信的2种解决方案,并给出了实现代码,需要的朋友可以参考下
    2014-10-10
  • Golang中tinyrpc框架的源码解读详解

    Golang中tinyrpc框架的源码解读详解

    tinyrpc是一个高性能的基于protocol buffer的rpc框架。项目代码非常少,很适合初学者进行golang的学习。本文将从源码的角度带大家了解tinyrpc框架的使用,需要的可以参考一下
    2023-01-01
  • golang的tunny的用法示例教程

    golang的tunny的用法示例教程

    这篇文章主要为大家介绍了golang的tunny的用法示例教程,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-07-07
  • Go语言无缓冲的通道的使用

    Go语言无缓冲的通道的使用

    Go语言中无缓冲的通道是指在接收前没有能力保存任何值的通道,本文主要介绍了Go语言无缓冲的通道的使用,具有一定的参考价值,感兴趣的可以了解一下
    2024-01-01
  • Go语言中的通道channel详情

    Go语言中的通道channel详情

    这篇文章主要介绍了Go语言中的通道channel,在Go语言中管道类似于一个数据流,每次放入或者取出一部分数据,数据取出后原通道内的数据就删除掉,在linux操作系统中管道会将函数的返回结果作为下一个函数的参数,下文详细内容需要的朋友可以参考下
    2022-02-02
  • Golang对mongodb进行聚合查询详解

    Golang对mongodb进行聚合查询详解

    这篇文章主要为大家详细介绍了Golang对mongodb进行聚合查询的方法,文中的示例代码讲解详细,对我们学习Golang有一点的帮助,需要的可以参考一下
    2023-02-02

最新评论