• Golang当中的定时器


    前言

    在平时写代码的时候,我们经常会遇到在将来某个时间点或者间隔一段时间重复执行函数。这个时候我们就可以考虑使用定时器。本片文章主要介绍一下golang当中的几个常用的定时器。time.Timer,time.Ticker,time.After以及time.AfterFunctime.Ticker的基本使用

    定时器的基本使用

    golang当中的定时器有这个一次性的定时器(Timer)和周期性的定时器(Ticker).在平时的编程当中经常会使用timer当中的ticker,AfterFunc定时器,而NewTicker是每隔多长时间触发,NewTimer是等待多长时间触发一次请注意是只触发一次。请注意一下两者的区别。
    下面我们来首先来使用一下这两个定时器首先是这个Timer定时器

    package main
    
    import (
    	"fmt"
    	"time"
    )
    func main() {
    	myTimer := time.NewTimer(time.Second * 3) //初始化定时器
    	var i = 0
    	for {
    		select {
    		case <-myTimer.C:
    			i++
    			fmt.Printf("the counter is%d", i)
    			myTimer.Reset(time.Second * 3) //注意需要重新设置
    		}
    	}
    	myTimer.Stop() //不在使用需要将其停止
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    注意这个timer定时器超时之后需要重新进行设置,才能重新触发。如果上面的代码我们没有Reset,那么就会导致死锁。其实我们也可以看看这个Timer是怎么是实现的

    func NewTimer(d Duration) *Timer {
        c := make(chan Time, 1)
        t := &Timer{
            C: c,  // 信道
            r: runtimeTimer{
                when: when(d),  // 触发时间
                f:    sendTime, // 时间到了之后的调用函数
                arg:  c,        // 调用sendTime时的入参
            },
        }
        startTimer(&t.r)  // 把定时器的r字段放入由定时器维护协程维护的堆中
        return t
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    从上面的构造函数中可以大概看出定时器的工作流程,这里面最重要的是runtimeTimer。构造定时器的时候会把runtimeTimer放入由定时器维护协程维护的堆中,当时间到了之后,维护协程把r从堆中移除,并调用r的sendTime函数,sendTime的入参是定时器的信道C。可以推断,sendTime中执行的逻辑应该是向信道C中推送时间,通知上游系统时间到了,而事实正是如此:

    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:
        }
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    其实这个time.After就是对这个time.Timer的一个封装,所以如果我们上面使用这个time.After那么会频繁的创建time.Timer对象
    下面我们在来看一下这个time.AterFunc()定时器。
    Golang当中的AfterFunc函数用于等待经过时间,此后在其自己的协程当中调用定义的函数f.函数在时间包下定义。下面我们一起看看如何使用这个

    import (
    	"fmt"
    	"time"
    )
    
    func main() {
    	f := func() {
    		fmt.Println("the func is call after 3 second")
    	}
    	myTime := time.AfterFunc(time.Second*3, f)
    
    	defer myTime.Stop() //定时器不用了需要关闭
    	time.Sleep(time.Second * 4)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    下面我们在看看这个time.NewTicker定时器的使用

    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    func main() {
    	mytick := time.NewTicker(time.Second * 2)
    	defer mytick.Stop() //定时器不用了需要关闭
    	done := make(chan struct{})
    	go func() {
    		for {
    			time.Sleep(time.Second * 10)
    			done <- struct{}{}
    		}
    	}()
    	for {
    		select {
    		case <-done:
    			fmt.Println("done!!!!")
    			return
    		case t := <-mytick.C:
    			fmt.Printf("the curtime is %v\n", t)
    		}
    	}
    
    }
    
    
    • 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
    • 28
    • 29

    下面我们来看一下这个陷阱,这个需要注意

    package main
    
    import (
    	"fmt"
    	"time"
    )
    func main() {
    	var count int
    	for {
    		select {
    		case <-time.Tick(time.Second * 1):
    			fmt.Println("case1")
    			count++
    			fmt.Println("count--->", count)
    		case <-time.Tick(time.Second * 2):
    			fmt.Println("case2")
    			count++
    			fmt.Println("count--->", count)
    		}
    	}
    	
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    这个代码是有陷阱的,下面我们来看看这个运行结果是什么?
    在这里插入图片描述
    可见 case2 永远没有被执行到,问题就出在代码逻辑上,首先看time.Tick方法。我们可以看一下这个方法就知道

    func Tick(d Duration) <-chan Time {
    	if d <= 0 {
    		return nil
    	}
    	return NewTicker(d).C
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    它每次都会创建一个新的定时器,随着 for 循环进行, select 始终监听两个新创建的定时器,老的定时器被抛弃掉了,也就不会去读取老定时器中的通道。
    select 可以同时监听多个通道,谁先到达就先读取谁,如果同时有多个通道有消息到达,那么会随机读取一个通道,其他的通道由于没有被读取,所以数据不会丢失,需要循环调用 select 来读取剩下的通道。

    总结:

    • tick创建完成之后,不是马上有一个tick.第一个tick在你设置的多少秒之后才会进行创建
    • golang当中的定时器实质上是这个单项的管道
    • time.NewTicker会定时触发任务,当下一次执行到来而当前任务画面执行完,会等待当前任务执行完毕在进行下一次任务。
    • Ticker和Timer的不同之处是,Ticker时间到达之后不需要人为的调用Reset方法来重新设置时间
  • 相关阅读:
    第二部分—C语言提高篇_12. 动/精态库的封装和使用
    C++下基于遗传算法解决TSP问题
    泛在电力物联网形势下考虑多主体投资的配电网运行优化策略
    【linux】【platform[1]】简述device和driver几种匹配方式(包括测试用demo)
    解决table表格td中文字过长自动换行问题
    【微积分】算法数学基础之微积分
    矩阵论理论知识(三)特殊的线性空间
    Xshell+screen解决ssh连接 服务器掉线的问题
    FH7333系列LDO稳压IC系列
    【C语言】指针的进阶(三)—— 模拟实现qsort函数以及指针和数组的笔试题解析
  • 原文地址:https://blog.csdn.net/qq_56999918/article/details/130499688