Go语言中Timer计时器的使用技巧详解

 更新时间:2023年07月25日 10:56:26   作者:cainmusic  
Go语言中的time包里有个Timer计时器的功能,这篇文章主要就是来和大家介绍一下Timer计时器的使用技巧,感兴趣的小伙伴可以跟随小编一起学习一下

time包里有个Timer计时器的功能,主要的结构和函数有:

type Timer struct {
    C <-chan Time
    r runtimeTimer
}
func After(d Duration) <-chan Time
func AfterFunc(d Duration, f func()) *Timer
func NewTimer(d Duration) *Timer
func (*Timer) Reset(d Duration) bool
func (*Timer) Stop() bool

三个基本用法:

c := time.After(time.Second)
fmt.Println(<-c)
t := time.NewTimer(time.Second)
fmt.Println(<-t.C)
tc := make(chan int)
time.AfterFunc(time.Second, func() { tc <- 1 })
fmt.Println(<-tc)

After函数实际就是return NewTimer(d).C,和NewTimer的用法类似,但Timer本身还有ResetStop等方法可用,有相关需求的,应使用NewTimer

AfterFunc相当于在d Duration之后创建了一个执行f的goroutine,返回的Timer本身并不会阻塞,也不能像前面的例子那样使用Timer.C,但可以使用ResetStop等方法。

导致上面区别的原因在于使用NewTimerAfterFunc生成计时器的时候,内部使用的调用参数并不相同。

NewTimer:

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

NewTimer在计时器完成时使用sendTime函数,非阻塞的向Timer.C中传入当前时间,所以在计时器完成时,可以从其中获取内容。

AfterFunc:

func AfterFunc(d Duration, f func()) *Timer {
    t := &Timer{
        r: runtimeTimer{
            when: when(d),
            f:    goFunc,
            arg:  f,
        },
    }
    startTimer(&t.r)
    return t
}
func goFunc(arg interface{}, seq uintptr) {
    go arg.(func())()
}

AfterFunc则是在计时器完成时调用goFunc,在goFunc中启动一个执行参数f的goroutine,而并未对Timer.C进行任何操作,于是我们无法从其中获取内容。

注:下面的内容主要基于NewTimer创建的Timer

Timer使用的关键点:

一,在一些任务中我们需要多次重复计时,不要使用循环创建大量计时器,会影响性能,尽量使用ResetStop来复用已创建的计时器。

二,TimerStop方法并不会关闭Timer.C,可能会导致意外的阻塞,如:

func main() {
    timer := time.NewTimer(time.Second)
    go func() {
        timer.Stop()
    }()
    <-timer.C
}

会导致程序阻塞,无法退出。

关于TimerResetStop的使用小技巧:

// 用下面的非阻塞方法使用Stop
func timerStop(t *time.Timer) {
    if !t.Stop() {
        select {
        case <-t.C:
        default:
        }
    }
}
// Reset之前先执行Stop
func timerReset(t *time.Timer, d time.Duration) {
    timerStop(t)
    t.Reset(d)
}

关于Reset之前为何要Stoptime包的Reset文档如下说:

For a Timer created with NewTimer, Reset should be invoked only on stopped or expired timers with drained channels.

对于使用NewTimer创建的Timer,Reset应该用在已经停止或过期,并已经排空管道的计时器上。

If a program has already received a value from t.C, the timer is known to have expired and the channel drained, so t.Reset can be used directly. If a program has not yet received a value from t.C, however, the timer must be stopped and—if Stop reports that the timer expired before being stopped—the channel explicitly drained:

如果一个程序已经从t.C中接收了值,计时器过期了并且管道已被排空,Reset可以直接使用。但如果程序还未从t.C中接收值,而计时器需要被停止,并且Stop方法报告计时器在被停止前已经过期,则管道需要被显式的排空:

if !t.Stop() {
    <-t.C
}
t.Reset(d)

This should not be done concurrent to other receives from the Timer's channel.

这个操作不应与其他程序接收计时器的管道同时发生。

注意,上面的内容其实还没表述完全。

如果我们需要停止一个计时器,并且计时器的Stop方法报告为false时,计时器的状态,以及t.C的状态,共有三种可能:

  • Stop前已经被Stop,t.C为空
  • Stop前已经过期,计时器向t.C中写入内容,t.C为满
  • Stop前已经过期,计时器向t.C中写入内容,t.C的信息已被其他程序接收,t.C为空

前面文档中的程序,仅在第2种情况会按照预期运行。

其他两种情况,显式排空<-t.C的时候会阻塞。

就是因为上面的情况,才演化出前面的timerStop函数。

但同时应该明白,timerStop函数对应上面几种情况时如何处理:

  • select走default分支,跳过阻塞,但应考虑到计时器并不是当前Stop停止的
  • select进行显式排空,但应考虑到计时器并未被成功停止,并且t.C的内容被抛弃了
  • select走default分支,跳过阻塞,但应考虑到计时器并未被成功停止,并且t.C的内容被其他程序利用了

充分的考虑到上面这几点,就可以使用timerStop函数了。

否则,应该充分考虑自己程序的需求,进行必要的修改。

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

相关文章

  • Golang中interface转string输出打印方法

    Golang中interface转string输出打印方法

    这篇文章主要给大家介绍了关于Golang中interface转string输出打印的相关资料,在go语言中interface转string可以直接使用fmt提供的fmt函数,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2024-02-02
  • Go 第三方库之类型转换问题

    Go 第三方库之类型转换问题

    今天给大家介绍一个第三方库,专门处理类型转换的问题。对Go 第三方库之类型转换问题感兴趣的朋友跟随小编一起看看吧
    2021-08-08
  • 拦截信号Golang应用优雅关闭的操作方法

    拦截信号Golang应用优雅关闭的操作方法

    这篇文章主要介绍了拦截信号优雅关闭Golang应用,本文介绍了信号的概念及常用信号,并给出了应用广泛的几个示例,例如优雅地关闭应用服务、在命令行应用中接收终止命令,需要的朋友可以参考下
    2023-02-02
  • 深入了解Golang的map增量扩容

    深入了解Golang的map增量扩容

    这篇文章主要介绍了深入了解Golang的map增量扩容,扩容的主要目的是为了缩短map容器的响应时间。增量扩容的本质其实就是将总的扩容时间分摊到了每一次hash操作上,更多相关内容需要的小伙伴可以参考一下
    2022-06-06
  • GO语言延迟函数defer用法分析

    GO语言延迟函数defer用法分析

    这篇文章主要介绍了GO语言延迟函数defer用法,较为详细的分析了GO语言的特性与具体用法,并给出了一个比较典型的应用实例,具有一定的参考借鉴价值,需要的朋友可以参考下
    2014-12-12
  • go带缓冲chan实现消息队列功能

    go带缓冲chan实现消息队列功能

    本文主要介绍了go带缓冲chan实现消息队列功能,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-02-02
  • golang语言map全方位介绍

    golang语言map全方位介绍

    本文主要介绍了golang语言map全方位介绍,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-01-01
  • 浅谈Go语言的高效编码细节

    浅谈Go语言的高效编码细节

    这篇文章主要介绍了浅谈Go语言的高效编码细节,我们都知道golang是天生的高并发,高效的编译型语言,可我们也都可知道,工具再好,用法不对,全都白费,我们来举2个常用路径来感受一下
    2023-01-01
  • Go gRPC教程实现Simple RPC

    Go gRPC教程实现Simple RPC

    这篇文章主要为大家介绍了Go gRPC教程实现Simple RPC示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-06-06
  • go实现图片拼接与文字书写的方法实例

    go实现图片拼接与文字书写的方法实例

    这篇文章主要给大家介绍了关于go实现图片拼接与文字书写的相关资料,文中通过实例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2022-01-01

最新评论