当在每个循环中使用go例程时,去由func文字捕获的兽医范围变量

时间:2016-10-30 06:56:27

标签: go

我不确定“func”字面是什么'因此这个错误令我感到困惑。我想我看到了这个问题 - 我在新的例行程序中引用了一个范围值变量,因此价值可能会随时改变,而不是我们所期望的。什么是解决问题的最佳方法?

有问题的代码:

func (l *Loader) StartAsynchronous() []LoaderProcess {
    for _, currentProcess := range l.processes {
        cmd := exec.Command(currentProcess.Command, currentProcess.Arguments...)
        log.LogMessage("Asynchronously executing LoaderProcess: %+v", currentProcess)
        go func() {
            output, err := cmd.CombinedOutput()
            if err != nil {
                log.LogMessage("LoaderProcess exited with error status: %+v\n %v", currentProcess, err.Error())
            } else {
                log.LogMessage("LoaderProcess exited successfully: %+v", currentProcess)
                currentProcess.Log.LogMessage(string(output))
            }
            time.Sleep(time.Second * TIME_BETWEEN_SUCCESSIVE_ITERATIONS)
        }()
    }
    return l.processes
}

我建议的修复:

func (l *Loader) StartAsynchronous() []LoaderProcess {
    for _, currentProcess := range l.processes {
        cmd := exec.Command(currentProcess.Command, currentProcess.Arguments...)
        log.LogMessage("Asynchronously executing LoaderProcess: %+v", currentProcess)
        localProcess := currentProcess
        go func() {
            output, err := cmd.CombinedOutput()
            if err != nil {
                log.LogMessage("LoaderProcess exited with error status: %+v\n %v", localProcess, err.Error())
            } else {
                log.LogMessage("LoaderProcess exited successfully: %+v", localProcess)
                localProcess.Log.LogMessage(string(output))
            }
            time.Sleep(time.Second * TIME_BETWEEN_SUCCESSIVE_ITERATIONS)
        }()
    }
    return l.processes
} 

但这真的能解决问题吗?我只是将范围变量中的引用移动到另一个局部变量,该变量的值基于我所处的每个循环的迭代。

4 个答案:

答案 0 :(得分:27)

不要感到不好这是Go中新来者的常见错误,是的,每次循环的var currentProcess 都会发生变化,所以你的goroutines会使用最后一个进程。 slice l.processes ,您所要做的就是将变量作为参数传递给匿名函数,如下所示:

func (l *Loader) StartAsynchronous() []LoaderProcess {

    for ix := range l.processes {

        go func(currentProcess *LoaderProcess) {

            cmd := exec.Command(currentProcess.Command, currentProcess.Arguments...)
            log.LogMessage("Asynchronously executing LoaderProcess: %+v", currentProcess)

            output, err := cmd.CombinedOutput()
            if err != nil {
                log.LogMessage("LoaderProcess exited with error status: %+v\n %v", currentProcess, err.Error())
            } else {
                log.LogMessage("LoaderProcess exited successfully: %+v", currentProcess)
                currentProcess.Log.LogMessage(string(output))
            }

            time.Sleep(time.Second * TIME_BETWEEN_SUCCESSIVE_ITERATIONS)

        }(&l.processes[ix]) // passing the current process using index

    }

    return l.processes
}

答案 1 :(得分:3)

对于那些寻找简单示例的人:

这是错误的:

func main() {
  for i:=0; i<10; i++{
    go func(){
        processValue(i)
    }()
  }
}

func processValue(i int){
  fmt.Println(i)
}

这不完全是一个错误,但可能会导致意外行为,因为控制循环的变量 i 可以从其他go例程中更改。实际上,go vet command对此发出了警报。 Go vet可以精确地帮助您找到这种可疑的结构,它使用的启发式方法不能保证所有报告都是真正的问题,但是可以找到编译器未捕获的错误。因此,不时运行它是一个好习惯。

在运行代码之前,

Go Playground运行 govet ,您可以在运行中here看到它。

这是正确的:

func main() {
  for i:=0; i<10; i++{
    go func(differentI int){
        processValue(differentI)
    }(i)
  }
}

func processValue(i int){
  fmt.Println(i)
}

我故意将函数文字参数命名为 differentI ,以使其明显是另一个变量。这样做可以安全地并发使用,兽医不会抱怨,而且您不会有任何奇怪的行为。您可以看到它在运行here。 (由于打印是在不同的go例程上完成的,所以您什么也看不到,但是程序将成功退出)

顺便说一句, func文字基本上是一个匿名函数:)

答案 2 :(得分:2)

是的,您所做的是正确修复此警告的最简单方法。

在修复之前,只有一个单个变量,并且所有goroutine都指向它。这意味着他们没有看到它们开始时的值,而是当前值。在大多数情况下,这是范围内的最后一个。

答案 3 :(得分:-1)

如果您有使用JS中的Javascript和闭包的经验,则可以将它与for循环内的setTimeout的经典示例与setTimeout进行比较,并且很容易将其与setTimeout进行比较,并在迭代结束时使用循环var的最终值