golang的timer的一些坑

本文介绍了在Golang中实现定时任务时遇到的问题,如定时器无限循环。分析了问题原因在于`time.Timer`的`Stop`方法仅移除定时器,不关闭通道。提出了两种解决方案:一是通过检查`Stop`返回值并关闭通道来确保单次执行;二是根据当前时间动态调整下次执行时间。此外,还给出了每周一10点执行任务的实现方法。
摘要由CSDN通过智能技术生成

需求

最近项目有一些定时任务的需求,如每天的早上10:00:00定时的去执行一些任务。

问题

但是我遇到了一些问题,就是我定时10:00:00去执行,但是这个定时器疯狂的翻滚停不下来了,代码如下

package main

import (
	"fmt"
	"time"
)

func main() {
	//每天10:05 通报昨日报警数量统计
	go func() {
		for {
			// 定时器会一直执行
			now := time.Now()
			// 计算明天的时间
			next := now.Add(time.Hour * 24 * time.Duration(0))
			// 10:00 进行提醒
			next = time.Date(next.Year(), next.Month(), next.Day(), 10, 0, 0, 0, next.Location())
			fmt.Println("now:", next)
			fmt.Println("next:", next)
			fmt.Println("next.Sub(now:", next.Sub(now))

			timer := time.NewTimer(next.Sub(now))
			select {
			case ts := <-timer.C:
				fmt.Println("Start YesterdayAlarmStatistics ts=%s", ts.String())
			}
		}
	}()

	for {

	}
}

控制台打印

Start YesterdayAlarmStatistics ts=%s 2022-04-02 14:49:34.505853 +0800 CST m=+1.806330418
now: 2022-04-02 10:00:00 +0800 CST
next: 2022-04-02 10:00:00 +0800 CST
next.Sub(now: -4h49m34.505858s
Start YesterdayAlarmStatistics ts=%s 2022-04-02 14:49:34.505868 +0800 CST m=+1.806345209
now: 2022-04-02 10:00:00 +0800 CST
next: 2022-04-02 10:00:00 +0800 CST
next.Sub(now: -4h49m34.505872s
Start YesterdayAlarmStatistics ts=%s 2022-04-02 14:49:34.505882 +0800 CST m=+1.806359626
now: 2022-04-02 10:00:00 +0800 CST
next: 2022-04-02 10:00:00 +0800 CST...

它会疯狂的打印并不会停下来,可以看到下面这行代码里传递的值其实是个负数-4h49m34.505872s,但是定时器仍在执行,百思不得其姐

timer := time.NewTimer(next.Sub(now))

分析:
Golang 的NewTimer方法调用后,生成的timer会放入最小堆,一个后台goroutine会扫描这个堆,将到时的timer进行回调和channel。
而golang的timer的Stop方法, 是只负责把timer从堆里移除,不负责close 上面的channel。于是第一次执行完毕后,之后所有值我们传递的都是个负数,但是stop并没有关闭channel,导致代码仍在在运行,就是个无限for循环。

解决方案1:单次定时器

缺点:下面的方法会让定时器变为单次执行的定时器

package main

import (
	"fmt"
	"time"
)

func main() {
	//每天10:05 通报昨日报警数量统计
	go func() {
		for {
			// 定时器会一直执行
			now := time.Now()
			// 计算明天的时间
			next := now.Add(time.Hour * 24 * time.Duration(0))
			// 10:00 进行提醒
			next = time.Date(next.Year(), next.Month(), next.Day(), 14, 32, 10, 10, next.Location())
			fmt.Println("now:", next)
			fmt.Println("next:", next)
			fmt.Println("next.Sub(now:", next.Sub(now))

			timer := time.NewTimer(next.Sub(now))
			select {
			case ts := <-timer.C:
				fmt.Println("Start YesterdayAlarmStatistics ts=%s", ts.String())
				b := timer.Stop()
				if !b {
					fmt.Println("==============")
					ts := <-timer.C
					fmt.Println(ts)
				}
			}
		}
	}()

	for {

	}
}

解决方案2:每天10:05:00执行

其实是我代码里埋的坑,既然它会一直执行,但是执行时间我们总能确定吧?所以我们在本次执行之后,修改下次的执行时间就行了

go func() {
		for {
			now := time.Now()
			var next time.Time

			//每天10:05发送,
			//如果当前时间是10:05之前那么按当前时间生成定时器,
			//如果不是则说明该时间已经过了10:05,于是明天发送,按照明天的10:05生成定时器
			if now.Hour() < 10 || now.Hour() == 10 && now.Minute() < 5 {
				next = now
			} else {
				next = now.Add(time.Hour * 24)
			}

			//10:05:00 进行提醒
			next = time.Date(next.Year(), next.Month(), next.Day(), 10, 5, 0, 0, next.Location())

			logger.Info("YesterdayAlarmStatistics Now=%s Next=%s", now, next)
			timer := time.NewTimer(next.Sub(now))

			select {
			case ts := <-timer.C:
				go yourFunction()
				logger.Info("StartYesterdayAlarmStatistics ts=%s", ts.String())
				time.Sleep(120 * time.Second)
				timer.Stop()
			}
		}
	}()

扩展:每周一的10点执行

go func() {
		for {
			now := time.Now()

			nowWeekDay := int(now.Weekday())
			nowHour := now.Hour()

			//Current Date : 0,1,2,3,4,5,6
			//Next Monday  : 1,7,6,5,4,3,2

			addDay := (8 - nowWeekDay) % 7 //进程可能在任何时候重启

			//这类场景无需特殊处理,上面的公式得到的addDay==0
			/*if nowWeekDay == 1 && nowHour < 10 { //nowHour 小于 10 说明本周的排班还未更新,需要处理一下
				addDay = 0
			}*/
			if nowWeekDay == 1 && nowHour >= 10 { //nowHour 大于 10 说明本周的排班已更新,准备处理下周的即可
				addDay = 7
			}

			// 计算明天的时间
			next := now.Add(time.Hour * 24 * time.Duration(addDay))
			// 10:00 进行提醒
			next = time.Date(next.Year(), next.Month(), next.Day(), 10, 0, 0, 0, next.Location())

			// 测试环境,每天
			// timer := time.NewTimer(24 * 60 * 60 * time.Second)
			// 正式环境,每周一
			timer := time.NewTimer(next.Sub(now))
			select {
			case ts := <-timer.C:
				go UpdateOnduty() //目前只处理这个租户
				logger.Info("Start UpdateOnduty_7day ts=%s", ts.String())
				timer.Stop()
			}
		}
	}()

参考链接:https://www.cnblogs.com/jiangz222/p/11622495.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值