栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > .Net

golang的timer的一些坑

.Net 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

golang的timer的一些坑

golang的timer的一些坑
  • 需求
  • 问题
  • 解决方案1:单次定时器
  • 解决方案2:每天10:05:00执行
  • 扩展:每周一的10点执行

需求

最近项目有一些定时任务的需求,如每天的早上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 = 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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1023564.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号