Go 定时器

本文详细介绍了Go语言中的一次性定时器(Timer)和周期性定时器(Ticker),包括它们的使用场景、接口及底层实现。一次性定时器适用于设定超时和延迟执行,而周期性定时器适合执行定时任务和定时聚合任务。在使用Ticker时需要注意及时停止以避免资源泄露。此外,文章还提及了runtimeTimer在Go不同版本中的优化变化。
摘要由CSDN通过智能技术生成

定时器

Go语言的定时器分为两种:

  • 一次性定时器(Timer):定时器值计时一次,计时结束便停止运行
  • 周期性定时器(Ticker):定时器周期性的进行计时,除非主动停止,否则将永远运行

1.一次性定时器(Timer)

1.1 简介

Timer是一种单一事件的定时器,即经过指定的时间后触发一个事件,这个事件通过其本身提供的channel进行通知。
之所以叫单一事件,是因为Timer只执行一次就结束,这也是一次性定时器与周期性定时器最重要的区别。
通过timer.NewTimer(d Duration)可以创建一个Timer,参数即等待时间,时间到来后立刻触发一个事件

1.2 使用场景

1.2.1 设定超时时间

协程从管道读取数据时,如果管道内没有数据那么协程将被阻塞,一直等待管道中有数据写入;有的时候我们不希望
协程被永久阻塞,而是等待一个指定的时间,如果超过这段时间管道内仍没有数据写入,则协程可以判定为超时,
转而去处理其他逻辑。

例子如下

package main

import (
	"fmt"
	"time"
)

func WaitChannel(conn <-chan string) bool{
	timer:=time.NewTimer(1*time.Second)
	select{
	case <-conn:
		timer.Stop()
		return true
	case <-timer.C:
		fmt.Println("WaitChannel timeout!")
		return false
	}
}


func main(){
	str:=make(chan string)
	str1:=make(chan string,1)

	str1<-"abc"

	ok1:=WaitChannel(str1)
	fmt.Println(ok1)

	ok:=WaitChannel(str)
	fmt.Println(ok)

}

image-20210727161313060

1.2.2 延迟执行某个方法

有的时候我们希望某个方法在今后某个时刻执行

package main

import (
	"fmt"
	"log"
	"time"
)

func DelayFunction(){
	timer:=time.NewTimer(5*time.Second)
	select{
	case <-timer.C:
		log.Println("Delay 5s,Start to do sth.")
	}
}

func main() {
	start:=time.Now()
	DelayFunction()
	cost:=time.Since(start)
	fmt.Println("cost",cost," s")
}

image-20210727163503985

1.3 Timer对外接口

  1. 创建定时器

    使用func NewTimer(d Duration) *Timer方法指定一个时间即可创建一个Timer,Timer一经创建便开始计时,不需要额外的启动命令。

    创建Timer意味着把一个计时任务交给系统守护协程,该协程管理着所有的Timer,

    当Timer的时间到达后向Timer的管道中发送当前的时间作为事件。

  2. 停止定时器

    Timer创建后可以随时停止,停止计时器方法如下:

    func(t *Timer) Stop() bool

    返回值代表定时器是否超时

    • true 定时器超时前停止,后续不会再发送事件
    • false 定时器超时后停止

    实际上,停止计时器意味着通知系统守护协程移除该定时器

  3. 重置定时器

    已过期的定时器或者已经停止的定时器可以通过重置动作重新激活,重置方法如下:

    func (t *Timer) Reset(d Duration) bool

    重置的动作实质上是先停止定时器,再启动,其返回值是停止计时器的返回值。

1.4. 简单接口

除了上面的标准接口,还提供了一些简单方法在特定情况下使用可以减少代码

  1. After()
  2. AfterFunc()

2. 周期性定时器(Ticker)

2.1 简介

Ticker是周期性定时器,即周期性的触发一个事件,通过Ticker本身提供的管道将事件传递出去。

2.2 使用场景

2.2.1 简单定时任务

有时我们希望定时执行一个任务,例如每秒记录一次日志

package main

import (
	"log"
	"time"
)

func TickerDemo(){
	ticker:=time.NewTicker(1*time.Second)
	defer ticker.Stop()

	for range ticker.C{
		log.Println("Ticker tick.")
	}
}


func main(){
	TickerDemo()
}

image-20210728154418662

for range语句会持续性地从管道中获取事件,收到事件后打印一行日志,如果管道中没有数据则会阻塞等待事件。由于Ticker会周期性地向管道写入事件,所以能实现周期性打印

2.2.2 定时聚合任务

有时我们希望把一些任务打包进行批量处理,例如下面的场景:

公交车发车遵循以下规则

  • 公交车每隔5分钟发车,不管是否已经坐满乘客
  • 已经坐满乘客的情况下,不足5分钟也会发车
package main

import (
	"bytes"
	"fmt"
	"math/rand"
	"time"
)

func TickerLaunch(){
	ticker:=time.NewTicker(5*time.Minute)
	maxPassenger:=30
	Passengers:=make([]string,0,maxPassenger)

	for{
		passenger:=GetNewPassenger(1)
		if passenger!=""{
			Passengers=append(Passengers,passenger)
		}else{
			time.Sleep(1*time.Second)
		}

		fmt.Println(Passengers)

		select {
		case<-ticker.C:
			Passengers=[]string{}

		default:
			if len(Passengers)>=maxPassenger{
				Passengers=[]string{}
			}

		}

		fmt.Println(Passengers)
	}
}



func GetNewPassenger(codeLen int) string{
	rawStr :="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_"
	buf := make([]byte, 0, codeLen)
	b := bytes.NewBuffer(buf)
	rand.Seed(time.Now().UnixNano())
	for rawStrLen := len(rawStr);codeLen > 0; codeLen-- {
		randNum := rand.Intn(rawStrLen)
		b.WriteByte(rawStr[randNum])
	}
	return b.String()
}


func main(){
	TickerLaunch()
}

具体看看逻辑就好,死循环不要轻易尝试运行

3. runtimeTimer

上面的两种计时器都会在底层创建一个runtimeTimer,所以每一个版本中runtimeTimer的优化都十分重要

  • Go 1.10之前:所有的runtimeTimer保存在一个全局的堆中;
  • Go 1.10~1.13: runtimeTimer被拆分到多个全局堆中 ,减少了多个系统协程的锁等待时间
  • Go 1.14+ : runtimeTimer保存在每个处理器P中,消除了专门的系统协程,减少了系统协程上下文切换的时间。

4. 注意事项

当我们使用Ticker的时候,如果忘记在使用结束后及时停止Ticker,就会造成资源泄露CPU使用率不断升高的情况

通常,我们在创建Ticker实例的时候就应该接着defer语句将Ticker停止

ticker:=time.NewTicker(1*time.Second)
defer ticker.Stop()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shelgi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值