Go to the vetering range variable captured by the func literal when using go mode inside each loop

I'm not quite sure what “func literal” is, so this error confuses me a bit. I think I see the problem - I am referring to the range value variable from within the new go procedure, so the value may change at any time and not be what we expect. What is the best way to solve the problem?

The code in question:

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 } 

My suggestion:

 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 } 

But is this really a solution to the problem? I just moved a link from a range variable to another local variable, the value of which is based on iteration for each loop that I am in.

+21
source share
3 answers

It's not bad that this is a common mistake for new users in Go, and yes the var variable currentProcess changes for each cycle, so your goroutines will use the last process in the l.processes slice, all you have to do is pass the variable as an anonymous parameter functions, for example:

 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 } 
+32
source

Yes, what you did is the easiest way to correctly fix this warning.

Prior to the correction, there was only one variable, and all the mountains referred to it. This means that they have not seen the value since their launch, except for the current value. In most cases, this is the last of the range.

+2
source

For those looking for a simpler example:

It is not right:

 func main() { for i:=0; i<10; i++{ go func(){ processValue(i) }() } } func processValue(i int){ fmt.Println(i) } 

Not quite a mistake, but it can lead to unexpected behavior, since the variable i controlling your loop can be changed from another go routine. This is actually a go vet team that warns about this. Go vet helps to accurately detect suspicious constructs of this kind, uses a heuristic that does not guarantee that all reports are genuine problems, but can find errors that were not detected by compilers. So it’s good practice to run it from time to time.

Go Playground starts before you run the code, check out , you can see it in action here .

It is right:

 func main() { for i:=0; i<10; i++{ go func(differentI int){ processValue(differentI) }(i) } } func processValue(i int){ fmt.Println(i) } 

I specifically named the func function parameter the literal DifferentI, so that it is obvious that this is another variable. It is safe to use at the same time, go vet will not complain and you will not behave strangely. You can see it in action here . (You will not see anything, since printing is performed on different subprograms, but the program will end successfully)

And by the way, func literal is basically an anonymous function :)

0
source

Source: https://habr.com/ru/post/1011853/


All Articles