Golang
主页 > 脚本 > Golang >

Go中的Timer和Ticker介绍

2024-07-07 | 佚名 | 点击:

一:简介

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

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

二、Timer:一次性定时器

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

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

下面的代码展示了如何使用 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 定时器触发啦!

下面是代码的逐步解析:

方法详解

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

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

下面是代码示例:

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

下面是代码的逐步解析:

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

下面是代码的逐步解析:

三: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

下面是代码的逐步解析:

除了使用 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 秒

下面是代码的逐步解析:

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("定时器停止啦!")

}

代码运行结果如下所示:

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

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

四:Timer 和 Ticker 的主要区别

用途:

行为特点:

可控性:

结束操作:

注意事项

原文链接:
相关文章
最新更新