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都在引用它。这意味着他们从开始时看不到值,而是当前值。在大多数情况下,这是该范围内的最后一个。