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

时间:2016-06-16 10:18:35

标签: go channel goroutine

下面是Stuff类型的结构。它有三个整数。 NumberDouble及其Power。让我们假装计算一个给定的int列表的双重和幂是一个昂贵的计算。

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
}

fmt.Println(stuff)的结果应该与初始化的内容相同:

stuff := []Stuff{
    {Number: 2, Double: 4, Power: 4}
    {Number: 3, Double: 6, Power: 9}
    {Number: 4, Double: 8, Power: 16}
}

我知道我可以使用<- double<- power来收集频道中的值,但我不知道双/权力属于哪些数字。

3 个答案:

答案 0 :(得分:6)

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

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

返回更多数据

我将使用chan Pair所在的频道类型Pair

type Pair struct{ Number, Result int }

所以计算将如下所示:

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

func powerNumber(i int, c chan Pair) { c <- Pair{i, i * i} }

我将使用map[int]*Stuff,因为可收集的数据来自多个渠道(doublepower),我想轻松快速地找到合适的Stuff(指针是必需的,所以我也可以在地图&#34;)中修改它。

所以主要功能:

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

输出(在Go Playground上尝试):

&{Number:2 Double:4 Power:4}
&{Number:3 Double:6 Power:9}
&{Number:4 Double:8 Power:16}

使用指针

由于我们现在正在传递*Stuff值,我们可以预先填写&#34; Stuff本身的输入数字。

但必须小心,只能通过适当的同步读/写值。最简单的是等待所有&#34;工人&#34; 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
}

输出(在Go Playground上尝试):

[{Number:2 Double:4 Power:4} {Number:3 Double:6 Power:9} {Number:4 Double:8 Power:16}]

答案 1 :(得分:1)

就个人而言,我会使用chan Stuff重新传递结果,然后旋转计算完整Stuff的goroutines并将其传回。如果您需要同时计算单个Stuff的各个部分,则可以使用专用通道从每个goroutine生成goroutine。收集完所有结果后,您可以(可选)使用累计值对切片进行排序。

我在下面的意思示例(原则上,您可以使用sync.WaitGroup协调事项,但如果输入计数已知,则严格来说不需要它。)

type Stuff struct {
  number int64
  double int64
  square int64
}

// Compute a Stuff with individual computations in-line, send it out
func computeStuff(n int64, out chan<- Stuff) {
   rv := Stuff{number: n}
   rv.double = n * 2
   rv.square = n * n
   out <- rv
}

// Compute a Stuff with individual computations concurrent
func computeStuffConcurrent(n int64, out chan<- Stuff) {
  rv := Stuff{number: n}
  dc := make(chan int64)
  sc := make(chan int64)
  defer close(dc)
  defer close(sc)
  go double(n, dc)
  go square(n, sc)
  rv.double = <-dc
  rv.square = <-sc
  out <- rv
}

func double(n int64, result chan<- int) {
   result <- n * 2
}

func square(n int64, result chan<- int) {
  result <- n * n
}

func main() {
  inputs := []int64{1, 2, 3}
  results := []Stuff{}
  resultChannel := make(chan Stuff)

  for _, input := range inputs {
    go computeStuff(input, resultChannel) 
    // Or the concurrent version, if the extra performance is needed
  }

  for c := 0; c < len(inputs); c++ {
    results = append(results, <- resultChannel)
  }
  // We now have all results, sort them if you need them sorted
}

答案 2 :(得分:-1)

Ordered-concurrently 是一个 go 模块,它同时处理工作并按照提供的顺序返回数据。 https://github.com/tejzpr/ordered-concurrently

示例 - https://play.golang.org/p/hkcIuRHj63h

package main

import (
    concurrently "github.com/tejzpr/ordered-concurrently/v2"
    "log"
    "math/rand"
    "time"
)

type loadWorker int

// The work that needs to be performed
// The input type should implement the WorkFunction interface
func (w loadWorker) Run() interface{} {
    time.Sleep(time.Millisecond * time.Duration(rand.Intn(10)))
    return w
}

func main() {
    max := 10
    inputChan := make(chan concurrently.WorkFunction)
    output := concurrently.Process(inputChan, &concurrently.Options{PoolSize: 10, OutChannelBuffer: 10})
    go func() {
        for work := 0; work < max; work++ {
            inputChan <- loadWorker(work)
        }
        close(inputChan)
    }()
    for out := range output {
        log.Println(out.Value)
    }
}

免责声明:我是模块创建者