使用for范围内的goroutine运行范围

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:

https://play.golang.org/p/uuw7r4kGBPb

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)
    }