Golang定时器Timer与Ticker的使用详解
作者:人艰不拆_zmc 发布时间:2024-04-27 15:27:44
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 个数据的时候便会被阻塞,以此保证不会被触发多次。
来源:https://www.cnblogs.com/zhangmingcheng/p/15817344.html


猜你喜欢
- 1.下载mysql的repo源$ wget http://repo.mysql.com/mysql-community-release-el
- 大致效果安装方式 IDEA插件官网地址:material theme ui GITHUB地址:material theme ui 下载之后选
- 背景:实现用python的optimize库的fsolve对非线性方程组进行求解。可以看到这一个问题实际上还是一个优化问题,也可以用之前拟合
- 动态语言与静态语言有很多不同,最大的特性之一就是可以实现动态的对类和实例进行修改,在Python中,我们创建了一个类后可以对实例和类绑定心的
- Python 提供了如下两个函数来检查类型:issubclass(cls, class_or_tuple):检查 cls 是否为后一个类或元
- 一、标准的数据集流程梳理分为几个步骤数据准备以及加载数据库–>数据加载器的调用或者设计–>
- 序言那个猥琐的家伙整天把个U盘藏着当宝,到睡觉了就拿出来插到电脑上。我决定想个办法看他U盘里都藏了什么,直接去抢U盘是不可能的,骗也是不可能
- 在数字图像中,往往存在着一些特殊形状的几何图形,像检测马路边一条直线,检测人眼的圆形等等,有时我们需要把这些特定图形检测出来,hough变换
- 本文实例讲述了php实现的验证码文件类。分享给大家供大家参考。具体如下:<?php/*** @file* @version 1.0*
- join toString该两种方法会将数组元素的类型转换为字符串var arr = [1, [2, [3, [4, 5]]], 6];co
- 删除一条留言信息会级联删除回复信息,这时我们需要用到事务,如下SQL 代码如下:ALTER PROCEDURE [dbo].[proc_tb
- 在之前文章给大家分享后不久,就有位小伙伴跟小编说在用scrapy搭建python爬虫中出现错误了。一开始的时候小编也没有看出哪里有问题,好在
- 在Python中,当我们有两个字典需要合并的时候,可以使用字典的 update 方法,例如:a = {'a': 1,
- 技巧之一:提高使用Request集合的效率 访问一个ASP集合来提取一个值是费时的、占用计算资源的过程。因为这个操作包含了一系列对相关集合的
- 直接赋值: 对象的引用,也就是给对象起别名浅拷贝: 拷贝父对象,但是不会拷贝对象的内部的子对象。深拷贝: 拷贝父对象. 以及其内部的子对象在
- 前言对于很多接触Python的人而言,字符的处理和语言整体的温顺可靠相比显得格外桀骜不驯难以驾驭。文章针对Python 2.7,主要因为3对
- 本文为大家分享了mysql 8.0.15 winx64安装配置方法,供大家参考,具体内容如下1.去官网下载最新版本的适用于window下的6
- 下面,小编将通过一组实例演示,让大家更直观,更清楚明白的了解要设置中文这一内容的操作步骤。首先展示实例代码:import pygamefro
- 从cmd进入python的方法如下1进入python打开cmd——>直接输入python即可,如下2退出python方法一:先按Ctr
- 1. MySQL的安装与配置:在Ubuntu下安装MySQL方法很简单,使用如下命令:sudo apt-get install mysql-