如何在Golang中实现Web服务后台异步定时触发并重启的功能?
需求与问题
需要在服务端实现以下功能:
- 后台运行,等待指定时长后触发事件
- 事件执行完成后自动重启定时器,循环往复
- 功能需异步执行:定时器在后台启动,到期后调用执行函数,函数执行完毕后异步重启定时器,不阻塞主代码
- 每次定时时长需动态计算
当前实现方案中,startTimer函数内的循环会阻塞调用它的goroutine(因<-jt.t.C是阻塞操作),无法满足异步不阻塞的要求,寻求改进方式。
现有代码示例
import ( "encoding/json" "fmt" "io/ioutil" "os" "strconv" "strings" "time" ) const INTERVAL_PERIOD time.Duration = 24 * time.Hour var HOUR_TO_TICK int var MINUTE_TO_TICK int var SECOND_TO_TICK int type jobTicker struct { t *time.Timer } func setConfig(appconfs map[string]string) bool { timeOfEventTrigger := appconfs["Task_Trigger_Time"] clockTime := strings.Split(timeOfEventTrigger, ":") fmt.Println("触发事件的固定时间点:", clockTime) HOUR_TO_TICK, _ = strconv.Atoi(clockTime[0]) MINUTE_TO_TICK, _ = strconv.Atoi(clockTime[1]) SECOND_TO_TICK, _ = strconv.Atoi(clockTime[2]) return true } func readConfigFromJson(configPath string) bool { fmt.Printf("从%s加载配置\n", configPath) configMap := map[string]string{} configfile, err := os.Open(configPath) if err != nil { fmt.Printf("加载配置失败:%v\n", err) return false } byteValue, err := ioutil.ReadAll(configfile) if err != nil { fmt.Printf("读取配置文件失败:%v\n", err) return false } err = json.Unmarshal(byteValue, &configMap) if err != nil { fmt.Printf("JSON解析失败:%v\n", err) return false } defer configfile.Close() return setConfig(configMap) } func getNextTickDuration() time.Duration { now := time.Now() nextTick := time.Date(now.Year(), now.Month(), now.Day(), HOUR_TO_TICK, MINUTE_TO_TICK, SECOND_TO_TICK, 0, time.Local) if nextTick.Before(now) { nextTick = nextTick.Add(INTERVAL_PERIOD) } fmt.Println("当前时间:", now) fmt.Println("下次触发时间:", nextTick) waitDuration := nextTick.Sub(time.Now()) fmt.Println("等待时长:", waitDuration) return waitDuration } func NewJobTicker() jobTicker { fmt.Println("生成新的任务定时器") return jobTicker{time.NewTimer(getNextTickDuration())} } func (jt jobTicker) updateJobTicker() { fmt.Println("更新任务定时器") jt.t.Reset(getNextTickDuration()) } func startTimer() { jt := NewJobTicker() for { <-jt.t.C fmt.Println(time.Now(), "- 触发事件") // 执行业务逻辑 jt.updateJobTicker() } }
配置文件示例
// config.json { "Task_Trigger_Time": "12:30:00" }
改进方案
核心问题是原startTimer函数的循环会阻塞调用方,只需将定时器循环逻辑放入独立goroutine中,即可实现异步不阻塞;同时优化结构体方法的接收者类型,避免值传递可能带来的混淆。
改进后的关键代码
- 修改
jobTicker的方法为指针接收者:
type jobTicker struct { t *time.Timer } func NewJobTicker() *jobTicker { fmt.Println("生成新的任务定时器") return &jobTicker{time.NewTimer(getNextTickDuration())} } func (jt *jobTicker) updateJobTicker() { fmt.Println("更新任务定时器") jt.t.Reset(getNextTickDuration()) }
- 异步启动定时器循环:
func startTimer() { // 启动独立goroutine处理定时器逻辑,不阻塞调用方 go func() { jt := NewJobTicker() for { <-jt.t.C fmt.Println(time.Now(), "- 触发事件") // 若业务逻辑耗时较长,可将其放入独立goroutine执行 // 确保业务逻辑完成后再重启定时器,符合需求 func() { // 这里执行你的业务逻辑 // do your logic here // 业务逻辑完成后重启定时器 jt.updateJobTicker() }() } }() }
说明
- 调用
startTimer()时,会立即返回,主程序不受阻塞,定时器逻辑在后台goroutine中运行 - 业务逻辑若耗时,可封装在内部goroutine中,避免阻塞定时器的下次触发判断(若需等待业务逻辑完成再重启定时器,则保持当前顺序即可)
- 使用指针接收者操作
jobTicker,确保对定时器的修改作用于原实例,避免值传递导致的无效操作
内容的提问来源于stack exchange,提问作者Ravi
相关产品推荐
相关产品推荐

