我不太确定'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: %+vn %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: %+vn %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
}
但这真的能解决问题吗?我只是将引用从range变量移动到另一个局部变量,该局部变量的值基于我所处的每个循环的迭代。
不要感到难过,这是Go新手的常见错误,是的,var currentProcess在每个循环中都会更改,因此您的例程将使用切片 1中的最后一个进程。处理,您所要做的就是将变量作为参数传递给匿名函数,如下所示:
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: %+vn %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
}
寻找更简单的例子:
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)
}
不完全是错误,但可能导致意外行为,因为控制循环的变量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)
}
我有意将函数字面值参数命名为differentI,以使它明显是一个不同的变量。这样做对于并发使用是安全的,go/strong>不会抱怨,你也不会得到奇怪的行为。你可以在这里看到它的作用。(由于打印是在不同的go例程中完成的,因此您将看不到任何内容,但程序将成功退出)
顺便说一下,func literal基本上是一个匿名函数:)是的,您所做的是正确修复此警告的最简单方法。
在修复之前,只有一个单个变量,并且所有的程序都引用它。这意味着他们看到的不是开始时的值,而是当前的值。在大多数情况下,这是范围内的最后一个。