广告位联系
返回顶部
分享到

Go中的Timer和Ticker介绍

Golang 来源:互联网 作者:佚名 发布时间:2024-07-07 20:00:19 人浏览
摘要

一:简介 在日常开发中,我们可能会遇到需要延迟执行或周期性地执行一些任务。这个时候就需要用到Go语言中的定时器。 在Go语言中,定时器类型有两种:一次性定时器time.Timer和 周期性定时

一:简介

在日常开发中,我们可能会遇到需要延迟执行或周期性地执行一些任务。这个时候就需要用到 Go 语言中的定时器。

在 Go 语言中,定时器类型有两种:一次性定时器time.Timer 和 周期性定时器time.Ticker 。本文将会对这两种定时器类型进行介绍。

二、Timer:一次性定时器

Timer 是一个一次性的定时器,用于在未来的某一时刻执行一次操作。

基本使用
创建 Timer 定时器的方式有两种:

  • NewTimer(d Duration) *Timer:该函数接受一个 time.Duration 类型的参数 d(时间间隔),表示定时器在过期(执行之后过期)之前等待的时间。NewTimer 返回一个新的 Timer 定时器,这个定时器在其内部维护一个通道 C,该通道在定时器被触发时会接收当前的时间值。
  • AfterFunc(d Duration, f func()) *Timer:接受一个指定的时间间隔 d 和回调函数 f。该函数返回一个新的 Timer 定时器,在定时器到期时直接调用 f,而不是通过通道 C 发送信号。调用 Timer 的 Stop 方法可以停止定时器和取消调用 f。

下面的代码展示了如何使用 NewTimer 和 AfterFunc 来创建定时器以及定时器的基本用法:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

package main

import (

    "fmt"

    "time"

)

func main() {

    // 使用 NewTimer 创建一个定时器,1s后往timer.C中发送当前时间

    timer := time.NewTimer(time.Second)

    gofunc() {

        select {

        case <-timer.C:

            fmt.Println("timer 定时器触发啦!")

        }

    }()

    // 使用 AfterFunc 创建另一个定时器,1s后执行func

    time.AfterFunc(time.Second, func() {

        fmt.Println("timer2 定时器触发啦!")

    })

    // 主goroutine等待两秒,确保看到定时器触发的输出

    time.Sleep(time.Second * 2)

}

代码运行结果如下所示:

timer 定时器触发啦!
timer2 定时器触发啦!

下面是代码的逐步解析:

  • 首先使用 NewTimer 创建了一个定时器,然后在一个新的goroutine 中监听它的 C 属性以等待定时器触发。
  • 其次,使用 AfterFunc 创建另一个定时器,通过指定一个 回调函数 来处理定时器到期事件。
  • 最后,主 goroutine 等待足够长的时间以确保定时器的触发信息能够被打印出来。

方法详解

Reset
Reset(d Duration) bool:该方法用于重置 Timer 定时器的过期时间,也可以理解为重新激活定时器。它接受一个 time.Duration 类型的参数 d,表示定时器在过期之前等待的时间。

除此之外,该方法还返回一个 bool 值:

  • 如果定时器处于活动的状态,返回 true。
  • 如果定时器已经过期或被停止了,返回 false(false 并不意味着激活定时器失败,只是标识定时器的当前状态)。

下面是代码示例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

package main

import (

    "fmt"

    "time"

)

func main() {

    timer := time.NewTimer(5 * time.Second)

    // 第一次重置,定时器处于激活状态,因此返回 true

    b := timer.Reset(1 * time.Second)

    fmt.Println(b) // true

    second := time.Now().Second()

    select {

    case t := <-timer.C:

        fmt.Println(t.Second() - second) // 1s

    }

    // 第二次重置,定时器已经处于过期状态,因此返回 false

    b = timer.Reset(2 * time.Second)

    fmt.Println(b) // false

    second = time.Now().Second()

    select {

    case t := <-timer.C:

        fmt.Println(t.Second() - second) // 2s

    }

}

代码运行结果如下所示:

true
1    
false
2

下面是代码的逐步解析:

  • 首先,创建了一个定时器,设置为 5 秒后到期。
  • 然后调用 Reset 方法立即将其重置为 1 秒后到期。因为此时定时器仍处于激活状态(即还未到期),所以Reset方法返回 true。
  • 接下来的 select 语句等待定时器到期,并打印出实际经过的秒数(约等于 1 秒)。
  • 接着第二次重置定时器,这次设置为 2 秒后到期。由于定时器在这次重置时已经到期,Reset 方法返回 false。
  • 最后,再次使用 select 语句等待定时器到期,并打印出这次经过的秒数(约等于 2 秒)。

Stop

Stop() bool:该方法用于停止定时器。如果定时器停止成功,返回 true,如果定时器已经过期或被已经被停止过,则返回 false。切记:Stop 操作不会关闭通道 C。这意味着无论是通过 for select 还是 for range 去监听 ticker.C,我们需要使用其他机制来退出循环,例如使用 context 上下文。下面是代码示例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

package main

import (

    "fmt"

    "time"

)

func main() {

    timer := time.NewTimer(3 * time.Second)

    // 停止定时器,在定时器触发之前停止它,因此返回 true

    stop := timer.Stop()

    fmt.Println(stop) // true

    stop = timer.Stop()

    // 第二次停止定时器,此时定时器已经被停止了,返回 false

    fmt.Println(stop) // false

}

代码运行结果如下所示:

true
false

下面是代码的逐步解析:

  • 首先,创建了一个设置为3秒后触发的定时器。
  • 然后立即调用Stop方法停止定时器。因为此时定时器还未触发,所以Stop返回 true。
  • 最后再次调用Stop 方法尝试停止同一个定时器。由于定时器已经被停止,这次 Stop 返回 false。

三:Ticker:周期性定时器

Tciker 是一个周期性的定时器,用于在固定的时间间隔重复执行任务。它在每个间隔时间到来时,向其通道(Channel)发送当前时间。

基本使用
我们可以使用 NewTicker 函数来创建一个新的 Ticker 对象,该函数接受一个 time.Duration 类型的参数 d(时间间隔)。

下面是代码示例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

package main

import (

    "context"

    "fmt"

    "time"

)

func main() {

    // 每隔1秒往ticker.C中发送当前时间

    ticker := time.NewTicker(time.Second)

    defer ticker.Stop()

    // 使用context控制下面的for select退出

    timeout, cancelFunc := context.WithTimeout(context.Background(), time.Second*5)

    defer cancelFunc()

    go func() {

        for {

            select {

            case <-timeout.Done():

                fmt.Println("timeout done")

                return

            case <-ticker.C:

                fmt.Println("定时器触发啦!")

            }

        }

    }()

    // 主goroutine等待 7 秒,确保看到定时器触发的输出

    time.Sleep(time.Second * 7)

}

代码运行结果如下所示:

定时器触发啦!
定时器触发啦!
定时器触发啦!
定时器触发啦!
定时器触发啦!
timeout done

下面是代码的逐步解析:

  • 首先,创建了一个每秒触发的定时器,确保函数周期结束后清理定时器,我们应该加上 defer ticker.Stop()
  • 然后,创建一个在 5 秒后超时的上下文。cancelFunc 被用于在退出前清理上下文。
  • 接着,在一个新的 goroutine 中,select 语句用于监听两个通道:定时器的通道 (ticker.C) 和超时上下文的完成通道 (timeout.Done())。当定时器每秒触发时,会打印出消息。当上下文超时(即 5 秒过后),打印出超时信息,并返回,从而结束该 goroutine。
  • 最后,主 goroutine 通过time.Sleep(time.Second * 7)等待 7 秒,以确保能够观察到定时器触发和超时事件的输出。

除了使用 select 语句监听 ticker.C 以外,我们还可以使用 for range 的形式进行监听:

for range ticker.C {}

需要注意的是,即使通过 Stop 方法停止 Ticker 定时器,其 C 通道不会被关闭。这意味着无论是通过 for select 还是 for range 去监听 ticker.C,我们需要使用其他机制来退出循环,例如使用 context 上下文。

方法详解

Reset
Reset(d Duration) 方法用于停止计时器并将其周期重置为指定的时间。下一个时间刻度将在新周期结束后生效。它接受一个 time.Duration 类型的参数 d,表示新的周期。该参数必须大于零;否则 Reset 方法内部将会 panic。

下面是代码示例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

package main

import (

    "time"

)

func main() {

    ticker := time.NewTicker(5 * time.Second)

    defer ticker.Stop()

    // 重置定时器

    ticker.Reset(1 * time.Second)

    second := time.Now().Second()

    for t := range ticker.C {

        // 1s

        fmt.Printf("周期:%d 秒", t.Second()-second)

        break

    }

}

代码运行结果如下所示:

周期:1 秒

下面是代码的逐步解析:

  • 首先,创建了一个每 5 秒触发一次的定时器 time.Ticker。
  • 其次,使用 Reset 方法重置定时器的触发间隔。5 秒变成 1 秒。
  • 最后,通过一次循环,打印定时器的周期,预期结果为 1 秒。

Stop
Stop() 方法用于停止定时器。在 Stop 之后,将不再发送更多的 tick 给其通道 C。切记:Stop 操作不会关闭通道 C。

下面是代码示例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

package main

import (

    "fmt"

    "time"

)

func main() {

    ticker := time.NewTicker(time.Second)

    quit := make(chanstruct{}) // 创建一个退出通道

    go func() {

        for {

            select {

            // ticker Stop后,不会往ticker.C中发送当前时间了,并且ticker.C并没有关闭,所以Stop后,不会再走该case了

            case <-ticker.C:

                fmt.Println("定时器触发啦!")

            // 关闭的chan是可以读的,读完chan中已有数据后,可以一直读出对应类型的零值,所以一旦quit被close,quit立马可读

            case <-quit:

                fmt.Println("协程停止啦!")

                return// 接收到退出信号,退出循环

            }

        }

    }()

    time.Sleep(time.Second * 3)

    ticker.Stop() // 停止定时器

    close(quit)   // 发送退出信号

    fmt.Println("定时器停止啦!")

}

代码运行结果如下所示:

定时器触发啦!
定时器触发啦!
定时器触发啦!
协程停止啦!
定时器停止啦!

  • 首先,创建一个每秒触发一次的 time.Ticker 对象。同时,引入了一个类型为 chan struct{} 的退出通道 quit。这个通道将用于向运行中的 goroutine 发送停止信号。
  • 其次,启动一个新的 goroutine。在这个 goroutine 中,使用for-select循环来监听两个事件:定时器的触发(case <-ticker.C)和退出信号(case <-quit)。每当定时器触发时,它会打印一条消息。如果收到退出信号,它会打印一条消息并退出循环。
  • 接着,在主goroutine中,time.Sleep(time.Second * 3) 模拟了一段等待时间(3 秒),在这期间定时器会触发几次。
  • 最后,主 goroutine 通过调用 Stop 方法停止定时器,然后关闭退出通道。goroutine 接收到退出信号后打印出一条消息并退出循环。

Stop 不会关闭其通道 C,因此我们需要借助其他方式(例如退出信号)来清理资源。

四:Timer 和 Ticker 的主要区别

用途:

  • Timer 用于单次延迟执行任务。
  • Ticker 重复执行任务。

行为特点:

  • Timer 在设定的延迟时间过后触发一次,发送一个时间值到其通道。
  • Ticker 按照设定的间隔周期性地触发,反复发送时间值到其通道。

可控性:

  • Timer 可以被重置(Reset 方法)和停止(Stop 方法)。
  • Reset 用于改变 Timer 的触发时间。
  • Ticker 可以被重置(Reset 方法)和停止(Stop 方法)。
  • Reset 用于改变 Ticker 触发的时间间隔。

结束操作:

  • Timer 的 Stop 方法用于阻止Timer触发,如果 Timer 已经触发,Stop 不会从其通道中删除已发送的时间值。
  • Ticker 的Stop方法用于停止 Ticker 的周期性触发,一旦停止,它不会再向通道发送新的值。

注意事项

  • 无论是 Timer 还是Ticker定时器,调用 Stop 方法之后,并不会关闭它们的 C 通道。如果有其他的 goroutine 在监听这个通道,为避免潜在的内存泄漏,需要手动结束该 goroutine。通常,这种资源释放的问题可以通过使用 context 或通过关闭信号(利用 Channel 实现)来解决。
  • 当Ticker定时器完成其任务后,为了防止内存泄漏,应调用 Stop 方法来释放相关资源。如果未及时停止 Ticker,可能导致资源持续占用。
  • 在编写 Go 代码时,我们应根据不同的应用场景去选择合适的定时器。同时,我们应遵循良好的规范,特别是在定时器使用完毕后及时释放资源,对于避免潜在的内存泄漏问题尤为重要。

版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。

您可能感兴趣的文章 :

原文链接 :
    Tag :
相关文章
  • 使用go语言实现Redis持久化的代码
    redis是一个内存数据库,如果你把进程杀掉,那么里面存储的数据都会消失,那么这篇文章就是来解决redis持久化的问题 我们在redis.conf文件
  • Go中的Timer和Ticker介绍
    一:简介 在日常开发中,我们可能会遇到需要延迟执行或周期性地执行一些任务。这个时候就需要用到Go语言中的定时器。 在Go语言中,定
  • Golang在gin框架中使用JWT鉴权
    什么是JWT JWT,全称 JSON Web Token,是一种开放标准(RFC 7519),用于安全地在双方之间传递信息。尤其适用于身份验证和授权场景。JWT 的设计
  • Golang使用Redis与连接池方式
    Golang使用Redis与连接池 使用下载go的redis包go get github.com/gomodule/redigo/redis 如果网不好的话就很费劲了 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 2
  • Golang使用embed引入静态文件的代码
    Go 语言从 1.16 版本开始引入了一个新的标准库embed,可以在二进制文件中引入静态文件 指令:/go:embed 通过一个简单的小实例,来演示将静态
  • Golang发送Get和Post请求的实现
    最近在研究钉钉机器人,发现钉钉的第三方接口有时需要用Get或者Post请求访问,虽然说可以通过Apifox直接模拟发送请求,但是我还是想研究
  • Go实现数据脱敏的方案设计
    在一些常见的业务场景中可能涉及到用户的手机号,银行卡号等敏感数据,对于这部分的数据经常需要进行数据脱敏处理,就是将此部分数
  • golang testing使用示例介绍
    testing包服务于自动化测试 基本测试 Table Drvien Test 基于表的测试通过表形式进行测试每种情况的输入和期望输出,从而测试程序的正确性
  • Golang流程控制语句的具体使用

    Golang流程控制语句的具体使用
    顺序控制 顺序控制 默认情况下,Go代码执行顺序是按照从上到下依次执行的,这种按照顺序执行的代码就叫做顺序语句。如下: 1 2 3 4 5 6
  • Golang图片验证码的使用方法介绍

    Golang图片验证码的使用方法介绍
    一、背景 最近在使用到Golang进行原生开发,注册和登录页面都涉及到图片验证码的功能。找了下第三方库的一些实现,发现了这个库用得还
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计