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

ano*_*932 21 go

我不太确定'func literal'是什么因此这个错误让我有点困惑.我想我看到了这个问题 - 我在一个新的go例程中引用了一个范围值变量,因此值可能随时改变,而不是我们所期望的.什么是解决问题的最佳方法?

有问题的代码:

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
}
Run Code Online (Sandbox Code Playgroud)

我建议修复:

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
} 
Run Code Online (Sandbox Code Playgroud)

但这真的解决了这个问题吗?我刚刚将范围变量的引用移动到另一个局部变量,该变量的值基于我所在的每个循环的迭代.

Yan*_*ozo 33

不要感觉不好这是Go中新来者的常见错误,是的,每个循环的var currentProcess都会发生变化,所以你的goroutines将使用切片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
}
Run Code Online (Sandbox Code Playgroud)

  • 感谢更新的代码看起来很棒!我的代码中有一个粗糙的错误,花了一个多小时修复与非常相似的东西.在没有意识到切片已经是指针之前,我正在返回[]*LoaderProcess,所以我实际上返回了一片指针,其中每个指针指向同一个LoaderProcess实例,这恰好是最后一个完成的命令.执行后每次都不同.从一段代码中学到了两个重要的经验教训.谢谢. (2认同)

Jai*_*ano 24

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

这是错误的:

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

        // Here i is a "free" variable, since it wasn't declared
        // as an explicit parameter of the func literal, 
        // so IT'S NOT copied by value as one may infer. Instead,
        // the "current" value of i
        // (in most cases the last value of the loop) is used
        // in all the go routines once they are executed.

        processValue(i)

    }()
  }
}

func processValue(i int){
  fmt.Println(i)
}
Run Code Online (Sandbox Code Playgroud)

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

Go Playground在运行代码之前先运行go vet,你可以在这里看到它的作用。

这是对的:

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

        processValue(differentI)

    }(i) // Here i is effectively passed by value since it was
         // declared as an explicit parameter of the func literal
         // and is taken as a different "differentI" for each
         // go routine, no matter when the go routine is executed
         // and independently of the current value of i.
  }
}

func processValue(i int){
  fmt.Println(i)
}
Run Code Online (Sandbox Code Playgroud)

我故意将 func 文字参数命名为differentI以使其明显是一个不同的变量。这样做对于并发使用是安全的,go vet不会抱怨并且你不会得到任何奇怪的行为。你可以在这里看到它的实际效果。(因为打印是在不同的 go 例程上完成的,所以你什么也看不到,但程序会成功退出)

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


Rol*_*lig 5

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

在修复之前,只有一个变量,所有 goroutine 都引用它。这意味着他们看到的不是开始时的值,而是当前的值。在大多数情况下,这是该系列中的最后一个。