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

时间:2019-07-17 15:59:30

标签: go

我在循环中有一个goroutine,我想为ms中的每个项目执行一个goroutine。条目可以同时执行,但仅在循环中的最后一个项目中运行。

我已经从一个更大的程序中提取了我的示例... {{3}}

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\n", 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\n", i, entry)
        ms.Entry = &entry
        for j, fn := range fns {
            fmt.Printf("fn loop %d\n", j)
            wg.Add(1)
            go ms.process(&wg, fn, fnChan)
        }
    }

    for d := range fnChan {
        fmt.Printf("fnchan: %+v\n", d)
    }
}

func (m *MyStruct) actionA() (string, error) {
    fmt.Println("actionA")
    fmt.Printf("Entry: %s\n", m.Entry.Name)
    return "actionA done", nil
}

func (m *MyStruct) actionB() (string, error) {
    fmt.Println("actionB")
    fmt.Printf("Entry: %s\n", 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}
}

2 个答案:

答案 0 :(得分:1)

问题

您在这里遇到问题:

ms.Entry = &entry

使用循环时,如下所示:

for i, entry := range *ms.Entries {

变量“ entry”仅声明一次。

因此&entry将具有一个恒定值(每次迭代中的值相同)。

但是,即使您解决了该问题,另一个问题是,每次迭代都使用相同的ms对象。

因此,当您随后启动不同的goroutine时,在第二次迭代中执行的ms.Entry = ...语句正在修改共享的ms对象。

您还将在迭代之间“共享” fn变量,如另一个答案中所述,因此您还应该捕获该变量。

修复

我建议您从结构中删除Entry字段(因为您已经拥有完整的数组,因此不需要它),并使用数组位置引用当前条目。

您应在“操作”和“过程”功能中添加一个i int参数。

您还需要“捕获” fn变量。

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

在此处查看经过修改的游乐场:

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

答案 1 :(得分:1)

您已被此陷阱困住:Using goroutines on loop iterator variables


一种解决方法:

    for j, fn := range fns {
        fmt.Printf("fn loop %d\n", 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)
    }