我不确定“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
}
但这真的能解决问题吗?我只是将范围变量中的引用移动到另一个局部变量,该变量的值基于我所处的每个循环的迭代。
答案 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的最终值