在for循环中无法将变量赋值给匿名函数。

huangapple go评论86阅读模式
英文:

Cannot assign variable to anonymous func in for loop

问题

我正在通过开发一个任务调度器来学习Go语言。我使用的cron库接受一个cron表达式和一个函数作为参数来添加调度器。

c.AddFunc("0 30 * * * *", func() { fmt.Println("每小时的半点") })

我正在开发的调度器根据一个yaml文件来调度任务。所以我遍历这些任务来添加调度器,代码如下:

type Job struct {
    Name        string
    Interval    string
}

func DistributeJob(job Job) {
    log.Println("运行中", job, job.Interval)
}

func main() {
    //从yaml加载配置
    c := cron.New()

    for _, job := range config.Jobs {
        c.AddFunc("@every "+job.Interval, func() {
            DistributeJob(job)
        })
        log.Println("任务" + job.Name + "已被调度!")
    }

    c.Start()
    select {}
}

所有的任务都按照它们的间隔被调度,但是结果却打印出了最后一个任务的描述。例如,如果我安排了两个任务,第一个间隔为3分钟,第二个间隔为1分钟。控制台输出如下:

12:01: 运行中 后者 1分钟
12:02: 运行中 后者 1分钟
12:03: 运行中 后者 1分钟
12:03: 运行中 后者 1分钟//这个应该是第一个任务

我认为问题出在以下代码段:

func() {
    DistributeJob(job)
})

似乎它只接受了最后一个任务,但我无法找出原因。我尝试使用以下代码:

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

但由于"cannot used as value"的错误而失败了。

英文:

I'm studying go-lang by developing a task schedular. The cron library I use accepts a cron expression and a func as parameters to add a scheduler.

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

The schedular I am developing schedules jobs according to a yaml file. So I iterate the jobs to add schedulars like this:

<pre>
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(&quot;@every &quot;+job.Interval, func() {
		DistributeJob(job)
	})
	log.Println(&quot;Job &quot; + job.Name + &quot; has been scheduled!&quot;)
}

c.Start()
select {}

}
</pre>

All jobs are scheduled on their intervals but it turns out that they're printing the last job's description. For example, if I schedule two jobs, the first interval on 3min and the latter interval on 1min. The console prints:

<pre>
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
</pre>

I think the problem is at

<pre>
func() {
DistributeJob(job)
})
</pre>

It seems it only takes the last job but I can't figure out why. I tried using

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

but it fails due to cannot used as value

答案1

得分: 9

当你没有使用goroutines时,你所犯的错误几乎与这里描述的错误相同:https://github.com/golang/go/wiki/CommonMistakes#using-closures-with-goroutines

引用一下:

上面循环中的val变量实际上是一个单一的变量,它取每个切片元素的值。因为闭包都只绑定到那一个变量,所以当你运行这段代码时,很有可能会看到最后一个元素被打印出来,而不是按顺序打印每个值,因为goroutines可能直到循环之后才开始执行。

所以你尝试的修复方法(将其作为参数传递给函数)原则上可以解决问题,但是你不能将带有参数的函数传递给cron库 - 带有参数的函数与不带参数的函数是不同的类型(此外,通过添加(),你实际上提前调用了函数并尝试传递其返回值)。

最简单的修复方法是为循环的每次迭代创建一个新变量,避免整个问题,像这样:

for _, job := range config.Jobs {
    realJob := job // 每次循环都创建一个新变量
    c.AddFunc("@every "+realJob.Interval, func() {
        DistributeJob(realJob)
    })
    log.Println("Job " + realJob.Name + " has been scheduled!")
}
英文:

While you are not using goroutines, the mistake you are making is almost identical to the one described here: https://github.com/golang/go/wiki/CommonMistakes#using-closures-with-goroutines

To quote:

> The val variable in the above loop is actually a single variable that takes on the value of each slice element. Because the closures are all only bound to that one variable, there is a very good chance that when you run this code you will see the last element printed for every iteration instead of each value in sequence, because the goroutines will probably not begin executing until after the loop.

So your attempted fix (to pass it as a parameter to your function) would in principle fix the problem, however you cannot pass a function with parameters to the cron library - a function with parameters is a different type from one without (in addition to which by adding the () you are actually calling the function in advance and trying to pass its return value).

The simplest fix is to create a new variable for every iteration of the loop and avoid the whole problem, like so:

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

huangapple
  • 本文由 发表于 2015年3月10日 09:50:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/28954869.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定