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 ... }
感觉很多面试公司在笔试中很喜欢出这种问题的面试题,如果没有了解到,很可能就会导致错误。