首页 > 其他分享 >定时器原理及使用

定时器原理及使用

时间:2023-12-28 22:44:21浏览次数:23  
标签:Reset 定时器 使用 timer Timer time 原理 main

一、引入

在进行并发编程时,有时候会需要定时功能,比如监控某个GO程是否会运行过长时间、定时打印日志等等。

GO标准库中的定时器主要有两种:Timer定时器、Ticker定时器。Timer计时器使用一次后,就失效了,需要Reset()才能再次生效。而Ticker计时器会一直生效。

二、Timer定时器

1)实现原理

在一个GO进程中,其中的所有计时器都是由一个运行着 timerproc() 函数的 goroutine 来保护。它使用时间堆(最小堆)的算法来保护所有的 Timer,其底层的数据结构基于数组的最小堆,堆顶的元素是间隔超时最近的 Timer,这个 goroutine 会定期 wake up,读取堆顶的 Timer,执行对应的 f 函数或者 sendtime()函数(下文会对这两个函数进行介绍),而后将其从堆顶移除。

Timer的结构:

type Timer struct {
    C <-chan Time
    // contains filtered or unexported fields
}

Timer中对外暴露的只有一个channel,这个 channel 也是定时器的核心。当计时结束时,Timer会发送值到channel中,外部环境在这个 channel 收到值的时候,就代表计时器超时了,可与select搭配执行一些超时逻辑。可以通过time.NewTimer、time.AfterFunc或者 time.Afte对一个Timer进行创建。

2)time.NewTimer()

简单的使用如下:

package main

import (
   "fmt"
   "time"
)
type H struct {
   t *time.Timer
}

func main()  {
   fmt.Println("main")
   h:=H{t: time.NewTimer(1*time.Second)}
   go h.timer()
   time.Sleep(10*time.Second)
}

func (h *H) timer()  {
   for  {
      select {
      case <-h.t.C:
         fmt.Println("timer")
      }
   }

}

创建了一个timer,设置时间为1S。然后使用一个select对timer的C进行接受,GO程运行timer()函数,会一直阻塞直到超时发生(接收到C的数据),此时打印timer。

Stop() 停止 Timer
func (t *Timer) Stop() bool

Stop()是 Timer 的一个方法,调用Stop()方法,会停止这个 Timer 的计时,使其失效,之后触发定时事件。

实际上,调用此方法后,此Timer会被从时间堆中移除。

Reset()重置Timer

注意,Timer定时器超时一次后就不会再次运行,所以需要调用Reset函数进行重置。修改select中代码,在Case中添加一个重置的代码:

select {
case <-h.t.C:
   fmt.Println("timer")
   h.t.Reset(1*time.Second)
}

可以看到,会不停的打印timer,这是因为使用了Reset函数重置定时器。

注意:不能随意的对Reset方法进行调用,官网文档中特意强调:

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

除非Timer已经被停止或者超时了,否则不要调用Reset方法,因为,如果这个 Timer 还没超时,不先去Stop它,而是直接Reset,那么旧的 Timer 仍然存在,并且仍可能会触发,会产生一些意料之外的事。所以通常使用如下的代码,安全的重置一个不知状态的Timer(以上的代码中,Reset调用时,总是处于超时状态):

if !t.Stop() {
    select {
    case <-h.t.C: 
    default:
    }
}
h.t.Reset(1*time.Second)

3)time.After()

此方法就像是一个极简版的Timer使用,调用time.After(),会直接返回一个channel,当超时后,此channel会接受到一个值,简单使用如下:

package main

import (
    "fmt"
    "time"
)

func main() {
    fmt.Println("main")
    go ticker()
    time.Sleep(100 * time.Second)
}

func ticker() {
    for {
        select {
        case <-time.After(1 * time.Second):
            fmt.Println("timer")
        }
    }
}

注意,此方法虽然简单,但是没有Reset方法来重置定时器,但是可以搭配for 和select的重复调用来模拟重置。

4)sendtime函数

NewTimer和After这两种创建方法,会Timer在超时后,执行一个标准库中内置的函数:sendTime,来将当前的时间发送到channel中。

5)time.AfterFunc

此方法可以接受一个func类型参数,在计时结束后,会运行此函数,查看以下代码,猜猜会出现什么结果?

package main

import (
   "fmt"
   "time"
)

func main() {
   fmt.Println("main")
   t := time.AfterFunc(1*time.Second, func() {
      fmt.Println("timer")
   })
   go timer(t)
   time.Sleep(10 * time.Second)
}
func timer(t *time.Timer) {
   select {
   case <-t.C:
      fmt.Println("123")
   }
}

结果只打印了main以及timer。这是因为此方法并不会调用上文提到的sendtime()函数,即不会发送值给Timer的Channel,所以select就会一直阻塞。

6)f函数

特意将AfterFunc和以上的NewTimer和After,就是因为f函数的存在。这种方式创建的Timer,在到达超时时间后会在单独的goroutine里执行函数f,而不会执行sendtime函数。

注意,外部传入的f参数并非直接运行在timerproc中,而是启动了一个新的goroutine去执行此方法。

三、Ticker定时器

Ticker定时器可以周期性地不断地触发时间事件,不需要额外的Reset操作,其使用方法与Timer大同小异。

需要注意的是每一个 NewTicker 方法开启的计时器都要在不需要使用时调用 Stop 进行关闭,如果不显示调用 Stop 方法,创建的计时器就没有办法被垃圾回收,而通过 Tick 创建的计时器由于只对外提供了 Channel,所以是一定没有办法关闭的,一定要谨慎使用这一接口创建计时器。

通过time.NewTicker对Ticker进行创建,简单的使用如下:

package main

import (
   "fmt"
   "time"
)

func main() {
   fmt.Println("main")
   t:=time.NewTicker(1*time.Second)
   go timer(t)
   time.Sleep(10 * time.Second)
}
func timer(t *time.Ticker) {
   for{
      select {
      case <-t.C:
         fmt.Println("timer")
      }
   }
}

 

标签:Reset,定时器,使用,timer,Timer,time,原理,main
From: https://www.cnblogs.com/beatle-go/p/17933755.html

相关文章

  • Vb.net 使用Webview2显示pdf文件
    使用webview2显示PDF文件需要wvliulanqi--Webview2控件的 AwaitWv2.EnsureCoreWebView2Async函数来启动浏览器否则会报错注意Div的宽度高度PrivateSubButton1_ClickAsync(senderAsObject,eAsEventArgs)HandlesButton1.ClickDimstrPathAs......
  • 一键控制变色台灯的工作原理?
    一键控制变色台灯的工作原理?单片机控制下的灯泡组单片机是一种简单计算机输入:按键,按一下表示+1运算:门电路通过控制电子和位移来进行运算。+1,输出一种结果;再+1,又输出别一种结果;如此循环往复。十进制计算机和二进制计算机的原理一模一样,试想一下,按一下代表齿轮旋转一个置,......
  • mrml 使用中的一些问题
    mrml对于mjml的兼容还是很不错的,目前是一些问题问题mjmlversion问题这个属于早期版本的问题了,目前使用方法已经不包含此参数了 <mjmlversion="3.3.3">应该去掉version其他配置参数mrml的实现与mjml的配置参数基本一致,有几个参数我们需要......
  • KEYSIGHT LCR使用visa通信的几个问题
    C#中使用visa网口与LCR通信1.在Keysight官网上下载IOLibrariessuite并安装,将C:\ProgramFiles\IVIFoundation\VISA\Win64\ktvisa\include\visa32.csC:\ProgramFiles\IVIFoundation\VISA\Win64\agvisa\agbin\visa32.dll拷贝到自己工程中,此dll为非托管,属性设置资......
  • 线程安全&&定时器总结
    总结线程线程:执行的独立代码线程执行是靠cpu分配时间片,同一个时间片上只能执行一个线程线程的状态:新建就绪运行阻塞死亡Thread:多线程的类currentThread()getName(),setName()sleep()实现多线程的方式1.继承Thread,重写run2.实现Runnable,重写run实现线程安全......
  • Go 泛型之明确使用时机与泛型实现原理
    目录一、引入二、何时适合使用泛型?场景一:编写通用数据结构时场景二:函数操作的是Go原生的容器类型时场景三:不同类型实现一些方法的逻辑相同时三、Go泛型实现原理Stenciling方案Dictionaries方案Go最终采用的方案:GCShapeStenciling方案四、泛型对执行效率的影响五、小结一......
  • svelte响应式原理
    svelte文件编译为js后的结构源代码:<scriptlang="ts">letfirstName='张'letlastName='三'letage=18functionhandleChangeName(){firstName='王'lastName='二'}fu......
  • 《FPGA原理和结构》——读书笔记
    最近做了一个关于FPGA的项目后,读了《FPGA原理和结构》这本书。主要梗概内容和想法如下。第一章:理解FPGA所需要的基础知识理解FPGA我们需要数电的组合逻辑、时序逻辑等内容的知识。FPGA(20世纪70年度发展起来的,因为其具有通过组合使用器件内大量的逻辑块来实现所需的电路,比以往侠......
  • Apipost-Helper使用流程
    Apipost-Helper是由Apipost推出的IDEA插件,写完接口可以进行快速调试,且支持搜索接口、根据method跳转接口,还支持生成标准的API文档,注意:这些操作都可以在代码编辑器内独立完成,非常好用!这里给大家介绍一下Apipost-Helper的安装和使用安装在IDEA编辑器插件中心输入Apipost搜索安装:Api......
  • 使用usb进行主从通信
    借鉴链接:https://www.cnblogs.com/pypyn/p/16945784.html gadget主机:usb主机控制器--主机控制器驱动(HCI,EHCI,)--usbcore--usb设备驱动--应用程序从机:usb设备控制器--设备控制器驱动(UDC)--gadgetAPI(驱动?)--待定使用usb转虚拟串口进行通信:首先理解了......