如何从按特定顺序执行的N个goroutines中收集值?

Jor*_*ran 7 channel go goroutine

下面是Stuff类型的结构.它有三个整数.A Number,它Double和它的Power.让我们假装计算给定的整数列表的双重和幂是一个昂贵的计算.

type Stuff struct {
    Number int
    Double int
    Power  int
}

func main() {
    nums := []int{2, 3, 4} // given numbers
    stuff := []Stuff{}     // struct of stuff with transformed ints

    double := make(chan int)
    power := make(chan int)

    for _, i := range nums {
        go doubleNumber(i, double)
        go powerNumber(i, power)
    }

    // How do I get the values back in the right order?

    fmt.Println(stuff)
}

func doubleNumber(i int, c chan int) {
    c <- i + i
}

func powerNumber(i int, c chan int) {
    c <- i * i
}
Run Code Online (Sandbox Code Playgroud)

结果fmt.Println(stuff)应该与初始化的东西相同,如:

stuff := []Stuff{
    {Number: 2, Double: 4, Power: 4}
    {Number: 3, Double: 6, Power: 9}
    {Number: 4, Double: 8, Power: 16}
}
Run Code Online (Sandbox Code Playgroud)

我知道我可以使用<- double<- power从频道收集价值,但我不知道什么双/权属于什么数字.

icz*_*cza 7

Goroutines独立地并发运行,因此如果没有明确的同步,您就无法预测执行和完成顺序.因此,您不能将返回的数字与输入数字配对.

您可以返回更多数据(例如输入数字和输出,例如包裹在结构中),或者将指针传递给工作者函数(作为新goroutine启动),例如*Stuff让goroutines填充Stuff自身中的计算数据.

返回更多数据

我将使用的信道类型chan Pair,其中Pair是:

type Pair struct{ Number, Result int }
Run Code Online (Sandbox Code Playgroud)

所以计算将如下所示:

func doubleNumber(i int, c chan Pair) { c <- Pair{i, i + i} }

func powerNumber(i int, c chan Pair) { c <- Pair{i, i * i} }
Run Code Online (Sandbox Code Playgroud)

我将使用一个map[int]*Stuff因为可收集数据来自多个通道(doublepower),我想找到合适的Stuff容易和快速(指针是必需的,所以我也可以在地图中修改它).

所以主要功能:

nums := []int{2, 3, 4} // given numbers
stuffs := map[int]*Stuff{}

double := make(chan Pair)
power := make(chan Pair)

for _, i := range nums {
    go doubleNumber(i, double)
    go powerNumber(i, power)
}

// How do I get the values back in the right order?
for i := 0; i < len(nums)*2; i++ {
    getStuff := func(number int) *Stuff {
        s := stuffs[number]
        if s == nil {
            s = &Stuff{Number: number}
            stuffs[number] = s
        }
        return s
    }

    select {
    case p := <-double:
        getStuff(p.Number).Double = p.Result
    case p := <-power:
        getStuff(p.Number).Power = p.Result
    }
}

for _, v := range nums {
    fmt.Printf("%+v\n", stuffs[v])
}
Run Code Online (Sandbox Code Playgroud)

输出(在Go Playground上试试):

&{Number:2 Double:4 Power:4}
&{Number:3 Double:6 Power:9}
&{Number:4 Double:8 Power:16}
Run Code Online (Sandbox Code Playgroud)

使用指针

从现在开始我们传递*Stuff值,我们可以"预先填充"输入数字Stuff本身.

但必须小心,只能通过适当的同步读/写值.最简单的是等待所有"工人"goroutines完成他们的工作.

var wg = &sync.WaitGroup{}

func main() {
    nums := []int{2, 3, 4} // given numbers

    stuffs := make([]Stuff, len(nums))
    for i, n := range nums {
        stuffs[i].Number = n
        wg.Add(2)
        go doubleNumber(&stuffs[i])
        go powerNumber(&stuffs[i])
    }
    wg.Wait()
    fmt.Printf("%+v", stuffs)
}

func doubleNumber(s *Stuff) {
    defer wg.Done()
    s.Double = s.Number + s.Number
}

func powerNumber(s *Stuff) {
    defer wg.Done()
    s.Power = s.Number * s.Number
}
Run Code Online (Sandbox Code Playgroud)

输出(在Go Playground上试试):

[{Number:2 Double:4 Power:4} {Number:3 Double:6 Power:9} {Number:4 Double:8 Power:16}]
Run Code Online (Sandbox Code Playgroud)

  • @JorgeBucaran是的,但是使用缓冲通道。对于大量的进程/作业,我将使用_producer-consumer_模式,其中工人goroutine的数量是固定的,并且已得到很好的隔离。有关示例,请参见[Bruteforce MD5密码破解程序](http://codereview.stackexchange.com/questions/114376/bruteforce-md5-password-cracker/116560#116560)上的答案。 (2认同)