在每个循环中使用go例程时,func文字捕获的go vet范围变量


go vet range variable captured by func literal when using go routine inside of for each loop

我不太确定" funcliteral"是什么,因此这个错误使我有些困惑。 我想我已经看到了问题-我是从新的go例程中引用一个范围值变量,因此该值可能随时更改,而不是我们期望的值。 解决问题的最佳方法是什么?

有问题的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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\
 %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
}

我建议的修正:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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\
 %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
}

但这真的可以解决问题吗? 我刚刚将引用从范围变量移到了一个不同的局部变量,其值基于我所参与的每个循环的迭代。


别担心,这是Go中新手经常遇到的错误,是的,每个循环都会改变var currentProcess,因此您的goroutine将使用slice l.processes中的最后一个进程,您要做的就是将变量传递为匿名函数的参数,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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\
 %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
2
3
4
5
6
7
8
9
10
11
func main() {
  for i:=0; i<10; i++{
    go func(){
        processValue(i)
    }()
  }
}

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

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

在运行代码之前,Go Playground运行go vet,您可以在此处看到实际操作。

这是对的:

1
2
3
4
5
6
7
8
9
10
11
func main() {
  for i:=0; i<10; i++{
    go func(differentI int){
        processValue(differentI)
    }(i)
  }
}

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

我故意将函数文字参数命名为differentI,以使其明显是另一个变量。以这种方式进行并发使用是安全的,兽医不会抱怨,您不会有任何奇怪的行为。您可以在这里看到实际效果。 (由于在不同的go例程上完成了打印,因此您将看不到任何内容,但是程序将成功退出)

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


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

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