corn 调度
本来想开发一个任务调度的服务,目标能够定时去运行一些任务。于是愉快的使用了 corn 包,并根据官网提供的 Demo 编写了自己的调度任务。
func main(){
c := cron.New(cron.WithSeconds())
c.AddFunc("*/5 * * * * *", func() {
fmt.Printf("我是任务1, time =%d\n",time.Now().Unix())
})
c.AddFunc("*/1 * * * * *", func() {
fmt.Printf("我是任务2)
})
c.Start()
select{}
}
调用 corn 对象的 AddFunc 方法就可以添加定时任务,分别设置了任务1每5秒执行一次,任务2每1秒执行一次。就这样很正常执行出结果。
动态加入调度
当我有很多个定时任务时,总不可能是有一个写一个 AddFunc 方法。所以可以使用反射方法,反射得到某个结构体的的方法,然后调用该方法。至于每个定时任务何时触发,可以使用 map 做一个定时任务名和定时时间的做 key-value 对应。
package task
//实现一个含有很多定时任务的结构体
type Task struct {}
func (Task) SyncTask1() {
log.Printf("我是任务1 time = %d\n", time.Now().Unix())
}
func (Task) SyncTask2() {
log.Printf("我是任务2")
}
package main
func main(){
var syncList = map[string]string{
"SyncTask1": "*/5 * * * * *",
"SyncTask2": "*/1 * * * * *",
}
funcs := reflect.ValueOf(&Task.Task{})
c := cron.New(cron.WithSeconds())
for key, val := range syncList {
c.AddFunc(val, func() {
f := funcs.MethodByName(key)
f.Call(nil)
})
c.Start()
time.Sleep(time.Second * 3)
}
select {}
}
本开开心心的执行以上代码,但是结果没有得到像图1上面的结果而是出现只调度执行任务2。
为什么只有任务2 执行
一开始我以为因为 for 循环这样动态加入定时任务 AddFunc 的原因导致其只执行一次,于是找了官网查看该方法说明,没有说需要什么参数去控制调度,只要调用 AddFunc 方法就会向管理器中加入定时任务,而后所有的定时任务就会依次执行。
于是将问题转移到 for range 循环上,在循环中打印出结果,打印时每次都能打印出值,没什么问题,但是如果打印 value 的内存地址时会发现 2次循环的内存地址都是一样。我增加内容,无论多少个值,value 的内存地址都是一样。
var arr = []string{"hi", "name", "asas", "sasa", "ffd"}
for key, value := range arr {
fmt.Println(key, value)
fmt.Println(&key, &value)
}
执行结果说明了,在 for 循环中其创建的变量是共享同一块内存地址。所以每次 key 、value 的内存地址都是一样的。如果 for 循环内的业务逻辑是同步的,就不会有什么影响,如果是异步的就会有影响。cron 每次创建一个定时任务都会创建一个新的 goroutine 来执行。这样就导致定时任务触发时访问到 value 都是 for 循环最后一次的值。
解决方案
for 循环中创建变量共享内存,那只需要每次循环时都创建一个新的变量,就可以很好的解决该问题。
for key, value := range arr {
tempKey, tempValue := key, value
...
}
感觉很多面试公司在笔试中很喜欢出这种问题的面试题,如果没有了解到,很可能就会导致错误。
本文正在参加「金石计划 . 瓜分6万现金大奖」