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计时器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • VS Code安装go插件失败原因分析以及解决方案

    VS Code安装go插件失败原因分析以及解决方案

    vscode安装go插件时,由于各种原因,在安装插件时总是失败,下面这篇文章主要给大家介绍了关于VS Code安装go插件失败原因分析以及解决的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-09-09
  • goland 断点调试显示“变量不可用”的问题解决

    goland 断点调试显示“变量不可用”的问题解决

    本文主要介绍了goland 断点调试显示“变量不可用”的问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-11-11
  • Go整合ElasticSearch的示例代码

    Go整合ElasticSearch的示例代码

    这篇文章主要介绍了Go整合ElasticSearch的相关知识,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-07-07
  • Golang构建WebSocket服务器和客户端的示例详解

    Golang构建WebSocket服务器和客户端的示例详解

    这篇文章主要为大家详细介绍了如何使用Go语言构建WebSocket服务器和客户端,以实现双向通信,文中的示例代码讲解详细,需要的小伙伴可以参考一下
    2023-11-11
  • 一文详解Go语言中的Defer机制

    一文详解Go语言中的Defer机制

    在Go语言中,defer是一个关键字,用于确保资源的清理和释放,特别是在函数中创建的资源,下面就跟随小编一起来了解下Defer机制的具体使用吧
    2024-11-11
  • Go语言配置解析库viper的使用指南

    Go语言配置解析库viper的使用指南

    viper 配置管理解析库,是由大神 Steve Francia 开发,本文就来和大家详细讲讲它的具体使用,文中的示例代码讲解详细,需要的可以收藏一下
    2023-06-06
  • go语言中读取配置文件的方法总结

    go语言中读取配置文件的方法总结

    这篇文章主要为大家详细介绍了go语言中读取配置文件的几个常见方法,文中的示例代码讲解详细,具有一定的借鉴价值,需要的小伙伴可以参考下
    2023-08-08
  • Golang内存管理之内存分配器详解

    Golang内存管理之内存分配器详解

    Go内存分配器的设计思想来源于TCMalloc,全称是Thread-Caching Malloc,核心思想是把内存分为多级管理,下面就来和大家深入聊聊Go语言内存分配器的使用吧
    2023-06-06
  • go语言通过zlib压缩数据的方法

    go语言通过zlib压缩数据的方法

    这篇文章主要介绍了go语言通过zlib压缩数据的方法,实例分析了Go语言中zlib的使用技巧,需要的朋友可以参考下
    2015-03-03
  • GO语言gin框架实现管理员认证登陆接口

    GO语言gin框架实现管理员认证登陆接口

    这篇文章主要介绍了GO语言gin框架实现管理员认证登陆接口,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-10-10

最新评论