为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
当年话下 2022-01-17 18:23:40
下面是 Stuff 类型的结构。它有三个整数。A Number,它的Double和它的Power。让我们假设计算给定整数列表的 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}的结果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 回答

?
侃侃无极

TA贡献2051条经验 获得超10个赞

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


您可以返回更多数据(例如,输入数字和输出,例如包装在一个结构中),或者将指针传递给工作函数(作为新的 goroutines 启动),例如让 goroutines 在其自身*Stuff中填充计算数据。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} }

我将使用 amap[int]*Stuff因为可收集的数据来自多个渠道(double和power),并且我想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])

}

输出(在Go Playground上试试):


&{Number:2 Double:4 Power:4}

&{Number:3 Double:6 Power:9}

&{Number:4 Double:8 Power:16}

使用指针

由于现在我们正在传递值,我们可以在其本身*Stuff中“预填充”输入数字。Stuff


但必须小心,您只能通过适当的同步读取/写入值。最简单的方法是等待所有“worker” goroutine 完成它们的工作。


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

同时写入不同的切片元素

另请注意,由于您可以同时编写不同的数组或切片元素(详细信息请参见我可以同时编写不同的切片元素),因此您可以将结果直接写入没有通道的切片中。请参阅重构代码以惯用的方式使用单个通道。


查看完整回答
反对 回复 2022-01-17
?
沧海一幻觉

TA贡献1824条经验 获得超5个赞

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


我在下面的意思示例(原则上,您可以使用 async.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

}


查看完整回答
反对 回复 2022-01-17
?
狐的传说

TA贡献1804条经验 获得超3个赞

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)

    }

}


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


查看完整回答
反对 回复 2022-01-17
  • 3 回答
  • 0 关注
  • 153 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信