如何在返回之前合并两个 goroutine 的结果?

huangapple go评论76阅读模式
英文:

How can I coalesce the results of two gorountines before returning?

问题

我在Go应用程序中有一个Web请求处理程序,需要向其他URL发出2个或更多的请求。我想收集每个URL的结果,将每个结果合并成一个JSON对象,并通过我的请求处理程序返回。这些请求不依赖于彼此,也不需要按顺序执行。

在Go中,最好的模式是什么?我应该使用通道和WaitGroup吗?

英文:

I have a web request handler in a Go app that needs to make 2+ requests to other URLs. I'd like to collect the results from each URL, coalesce each result into a single JSON object, and return via my request handler. The requests are not dependent upon each other and do not need to be sequenced.

What's the best pattern for doing this in Go? Should I use a channel and a WaitGroup?

答案1

得分: 1

对于简单的情况,我会使用一组本地变量和一些goroutine来设置这些变量,同时使用waitgroup来知道何时完成所有操作:

var a string
var b string
wg := sync.WaitGroup{}
wg.Add(2)
go func(){
    time.Sleep(5 * time.Second) // 发起请求
    a = "foo"
    wg.Done()
}()
go func(){
    time.Sleep(3 * time.Second) // 发起请求
    b = "bar"
    wg.Done()
}()
wg.Wait()
fmt.Println(a, b) // 结果合并

playground链接

如果你想要更复杂的行为,比如超时或部分结果,那么你可能希望子请求在一个通道上回传结果,然后你可以在select语句中选择处理:

// 确保缓冲区足够大,以便垃圾回收可以在超时时清理
ch := make(chan string, 2)
go func() {
    time.Sleep(5 * time.Second) // 发起请求
    ch <- "foo"
}()
go func() {
    time.Sleep(2 * time.Second) // 发起请求
    ch <- "bar"
}()
results := []string{}
timeout := time.After(4 * time.Second)
Loop:
for {
    select {
    case r := <-ch:
        results = append(results, r)
        if len(results) == 2 {
            break Loop
        }
    case <-timeout:
        break Loop
    }
}
fmt.Println(results)

playground链接

这样做不能完全保持顺序,但如果顺序很重要,你可以创建另一个通道。这是一般的思路。

英文:

For simple things I would use a set of local variables and some goroutines that set those variables, along with a waitgroup to know when everything is finished:

    var a string
    var b string
    wg := sync.WaitGroup{}
    wg.Add(2)
    go func(){
        time.Sleep(5 * time.Second) // make a request
        a = &quot;foo&quot;
        wg.Done()
    }()
    go func(){
        time.Sleep(3 * time.Second) // make a request
        b = &quot;bar&quot;
        wg.Done()
    }()
    wg.Wait()
    fmt.Println(a,b) //combine results

playground link

If you want more complicated behaviour like timeouts or partial results, then you probably want your subrequests to communicate results back on a channel you can select on:

// make sure to buffer to max number of senders so garbage collection can clean up
// if we time out
ch := make(chan string, 2)
go func() {
	time.Sleep(5 * time.Second) // make a request
	ch &lt;- &quot;foo&quot;
}()
go func() {
	time.Sleep(2 * time.Second) // make a request
	ch &lt;- &quot;bar&quot;
}()
results := []string{}
timeout := time.After(4 * time.Second)
Loop:
for {
	select {
	case r := &lt;-ch:
		results = append(results, r)
		if len(results) == 2 {
			break Loop
		}
	case &lt;-timeout:
		break Loop
	}
}
fmt.Println(results)

playground Link

That doesn't fully preserve order, but you could make another channel if that is important. Thats the general idea anyway.

答案2

得分: 0

我写了这个库,可以帮助简化并行运行Go协程,无需担心底层细节。你可以在这里找到它:https://github.com/shomali11/parallelizer

所以在你的情况下,你可以这样做:

package main

import (
	"github.com/shomali11/parallelizer"
	"fmt"
)

func main() {
	group := parallelizer.DefaultGroup()

    result1 := &SomeResultStructure{}
	group.Add(func(result *SomeResultStructure) {
		return func () {
            ...
            result.SomeValue = "1"
        }
	}(result1))

	result2 := &SomeResultStructure{}
	group.Add(func(result *SomeResultStructure) {
		return func () {
            ...
            result.SomeValue = "2"
        }
	}(result2))

	err := group.Run()

	fmt.Println("Done")
	fmt.Println(fmt.Sprintf("Results 1: %v", result1.SomeValue))
	fmt.Println(fmt.Sprintf("Results 2: %v", result2.SomeValue))
	fmt.Printf("Error: %v", err)
}

输出结果:

Done
Results 1: 1
Results 2: 2
Error: <nil>
英文:

I wrote this library that can help simplify running the go routines in parallel without having to worry about the low-level details https://github.com/shomali11/parallelizer

So in your case, you could do this:

package main

import (
    &quot;github.com/shomali11/parallelizer&quot;
    &quot;fmt&quot;
)

func main() {
	group := parallelizer.DefaultGroup()

    result1 := &amp;SomeResultStructure{}
    group.Add(func(result *SomeResultStructure) {
	    return func () {
            ...
            result.SomeValue = &quot;1&quot;
        }
    }(result1))

	result2 := &amp;SomeResultStructure{}
    group.Add(func(result *SomeResultStructure) {
	    return func () {
            ...
            result.SomeValue = &quot;2&quot;
        }
    }(result2))

    err := group.Run()

    fmt.Println(&quot;Done&quot;)
    fmt.Println(fmt.Sprintf(&quot;Results 1: %v&quot;, result1.SomeValue))
    fmt.Println(fmt.Sprintf(&quot;Results 2: %v&quot;, result2.SomeValue))
    fmt.Printf(&quot;Error: %v&quot;, err)
}

The output:

Done
Results 1: 1
Results 2: 2
Error: &lt;nil&gt;

huangapple
  • 本文由 发表于 2015年8月4日 02:54:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/31794707.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定