无法将变量分配给for循环中的匿名func

时间:2015-03-10 01:50:22

标签: go

我正在通过开发任务计划来学习go-lang。我使用的cron library接受一个cron表达式和一个func作为参数来添加一个调度程序。

c.AddFunc("0 30 * * * *", func() { fmt.Println("Every hour on the half hour") })

我正在开发的计划表根据yaml文件计划作业。所以我迭代这些作业来添加这样的日程表:

type Job struct {
    Name        string
    Interval    string

}

func DistributeJob(job Job) {
    log.Println("running", job, job.Interval)
}

func main() {
    //load config from yaml
    c := cron.New()

    for _, job := range config.Jobs {
        c.AddFunc("@every "+job.Interval, func() {
            DistributeJob(job)
        })
        log.Println("Job " + job.Name + " has been scheduled!")
    }

    c.Start()
    select {}
}

所有工作都按其间隔安排,但事实证明他们正在打印上一份工作的描述。例如,如果我安排两个作业,第一个间隔在3分钟,后一个间隔在1分钟。控制台打印:

12:01: Running latter 1min
12:02: Running latter 1min
12:03: Running latter 1min
12:03: Running latter 1min//this one should be the first job

我认为问题出在

    func() {
        DistributeJob(job)
    })

似乎只需要上一份工作,但我无法弄清楚原因。我尝试使用

    c.AddFunc("@every "+job.Interval, func(job JobType) {
        DistributeJob(job)
    }(job))

但由于无法用作值

而失败

1 个答案:

答案 0 :(得分:6)

虽然您没有使用goroutines,但您所犯的错误几乎与此处描述的错误相同:https://github.com/golang/go/wiki/CommonMistakes#using-closures-with-goroutines

引用:

  

上面循环中的val变量实际上是一个单独的变量,它接受每个slice元素的值。因为闭包只绑定到那个变量,所以很有可能当你运行这个代码时,你会看到每次迭代打印的最后一个元素而不是序列中的每个值,因为goroutines可能不会开始执行,直到循环之后。

因此,您尝试修复(将其作为参数传递给您的函数)原则上可以修复问题,但是您无法将带参数的函数传递给cron库 - 带参数的函数与不具有参数的函数不同(除此之外,通过添加(),您实际上是事先调用函数并尝试传递其返回值。)

最简单的解决方法是为循环的每次迭代创建一个新变量,并避免整个问题,如下所示:

for _, job := range config.Jobs {
    realJob := job // a new variable each time through the loop
    c.AddFunc("@every "+realJob.Interval, func() {
        DistributeJob(realJob)
    })
    log.Println("Job " + realJob.Name + " has been scheduled!")
}