I have a goroutine inside a loop and I want to execute a goroutine for each item in ms.Entries concurrently but it's only running for the last item in the loop.
I've abstracted my example out of a larger program... https://play.golang.org/p/whUMQ3pjq81
package main
import (
"fmt"
"sync"
)
type MyStruct struct {
Entry *Entry
Entries *[]Entry
}
type Entry struct {
ID int
Name string
}
type Fn struct {
Res string
Err error
}
func main() {
e1 := Entry{ID: 1, Name: "First"}
e2 := Entry{ID: 2, Name: "Second"}
ms := &MyStruct{
Entries: &[]Entry{e1, e2},
}
fmt.Printf("MS: %+v
", ms)
var wg sync.WaitGroup
fnChan := make(chan *Fn)
go func() {
wg.Wait()
close(fnChan)
}()
var fns []func() (string, error)
fns = append(fns, ms.actionA)
fns = append(fns, ms.actionB)
for i, entry := range *ms.Entries {
fmt.Printf("%d: %+v
", i, entry)
ms.Entry = &entry
for j, fn := range fns {
fmt.Printf("fn loop %d
", j)
wg.Add(1)
go ms.process(&wg, fn, fnChan)
}
}
for d := range fnChan {
fmt.Printf("fnchan: %+v
", d)
}
}
func (m *MyStruct) actionA() (string, error) {
fmt.Println("actionA")
fmt.Printf("Entry: %s
", m.Entry.Name)
return "actionA done", nil
}
func (m *MyStruct) actionB() (string, error) {
fmt.Println("actionB")
fmt.Printf("Entry: %s
", m.Entry.Name)
return "actionB done", nil
}
func (m *MyStruct) process(wg *sync.WaitGroup, fn func() (string, error), fnChan chan<- *Fn) {
fmt.Println("processing")
var err error
defer wg.Done()
res, err := fn()
if err != nil {
fnChan <- &Fn{Err: err}
return
}
fnChan <- &Fn{Res: res}
}
Problem
You have a problem here:
ms.Entry = &entry
When you use a loop, like this:
for i, entry := range *ms.Entries {
The variable "entry" is only declared once.
So &entry
will have a constant value (same value in every iteration).
But even if you solve that issue, another problem is that you are using the same ms
object on every iteration.
So when you then launch different goroutines, the ms.Entry = ...
statement you execute on the second iteration is modifying the shared ms
object.
You are also "sharing" the fn
variable between iterations as described in the other answer, so you should also capture that variable.
Fix
I suggest you remove the Entry
field from your struct (you don't need it since you already have the complete array), and you use the array position to refer to the current entry.
You should add an i int
parameter to your "action" and "process" functions.
You also need to "capture" the fn
variable.
for i, entry := range *ms.Entries {
...
for j, fn := range fns {
...
fn := fn // capture the fn variable
go ms.process(&wg, fn, fnChan, i) // sending index here
}
}
See here for a modified playground:
You have been caught by this trap : Using goroutines on loop iterator variables
One way to fix this :
for j, fn := range fns {
fmt.Printf("fn loop %d
", j)
wg.Add(1)
// copy the iterator variable to a local variable :
// variables declared within the body of a loop are not shared between iterations
f := fn
go ms.process(&wg, f, fnChan)
}