英文:
How get result of running the goroutine?
问题
在其他语言中,我可以同时运行多个任务,并将每个任务的结果存储在相应的变量中。
例如,在JavaScript中:
getApi1()
.then(res => console.debug("Result 1: ", res))
getApi2()
.then(res => console.debug("Result 2: ", res))
getApi3()
.then(res => console.debug("Result 3: ", res))
我知道每个函数执行的结果存储在哪个变量中。
在Python的asyncio中也是一样的:
task1 = asyncio.create_task(getApi1)
task2 = asyncio.create_task(getApi2)
result1 = await task1
result2 = await task2
我是Go语言的新手。所有的指南都说要在goroutine中使用通道。
但是我不明白,当我从通道中读取时,如何确保每个消息与哪个结果相匹配?
resultsChan := make(chan map)
go getApi1(resultsChan)
go getApi2(resultsChan)
go getApi3(resultsChan)
for {
result, ok := <-resultsChan
if ok == false {
break
} else {
// 在这里
fmt.Println(result) // 如何确定每个消息是哪个API请求的结果?
}
}
英文:
In other languages, I can run several tasks in concurrently and get the result of each in corresponding variable.
For example in JS:
getApi1()
.then( res => console.debug("Result 1: ", res) )
getApi2()
.then( res => console.debug("Result 2: ", res) )
getApi3()
.then( res => console.debug("Result 3: ", res) )
And I know exactly in which variable the result of the execution of which function.
The same is in Python asyncio:
task1 = asyncio.create_task(getApi1)
task2 = asyncio.create_task(getApi2)
result1 = await task1
result2 = await task2
I'm new in Go lang. All guides say to use channels with goroutines.
But I don't understand, when I read from the channel, how to be sure which message matches which result?
resultsChan := make(chan map)
go getApi1(resultsChan)
go getApi2(resultsChan)
go getApi3(resultsChan)
for {
result, ok := <- resultsChan
if ok == false {
break
} else {
// HERE
fmt.Println(result) // How to understand which message the result of what API request?
}
}
答案1
得分: 2
如何理解哪个API请求的结果是哪个消息?
如果同一个通道用于将所有getApiN
函数的结果传递给main
,并且您想要以编程方式确定每个结果来自哪里,您可以简单地为通道元素类型添加一个专用字段。下面,我声明了一个名为Result
的自定义结构类型,其中包含一个名为orig
的字段,用于此目的。
package main
import (
"fmt"
"sync"
)
type Origin int
const (
Unknown Origin = iota
API1
API2
API3
)
type Result struct {
orig Origin
data string
}
func getApi1(c chan Result) {
res := Result{
orig: API1,
data: "some value",
}
c <- res
}
func getApi2(c chan Result) {
res := Result{
orig: API2,
data: "some value",
}
c <- res
}
func getApi3(c chan Result) {
res := Result{
orig: API3,
data: "some value",
}
c <- res
}
func main() {
results := make(chan Result, 3)
var wg sync.WaitGroup
wg.Add(3)
go func() {
defer wg.Done()
getApi1(results)
}()
go func() {
defer wg.Done()
getApi2(results)
}()
go func() {
defer wg.Done()
getApi3(results)
}()
go func() {
wg.Wait()
close(results)
}()
for res := range results {
fmt.Printf("%#v\n", res)
}
}
可能的输出(结果的顺序不确定):
main.Result{orig:1, data:"some value"}
main.Result{orig:2, data:"some value"}
main.Result{orig:3, data:"some value"}
无论如何,我不建议遵循wic的建议;您的问题根本不适合使用反射。正如Rob Pike所说的那样:
反射从来都不是清晰的。你在Stack Overflow上经常看到的另一种情况是人们试图使用
reflect
,然后想知道为什么它不起作用。它不起作用是因为它不是为你准备的...很少有人应该使用反射。它是一个非常强大但非常难以使用的功能。[...]
英文:
> How to understand which message the result of what API request?
If the same channel is to communicate the results from all your getApiN
functions to main
and you want to programmatically determine where each result came from, you can simply add a dedicated field to your channel element type. Below, I've declared a custom struct type named Result
with a field named orig
for precisely that purpose.
package main
import (
"fmt"
"sync"
)
type Origin int
const (
Unknown Origin = iota
API1
API2
API3
)
type Result struct {
orig Origin
data string
}
func getApi1(c chan Result) {
res := Result{
orig: API1,
data: "some value",
}
c <- res
}
func getApi2(c chan Result) {
res := Result{
orig: API2,
data: "some value",
}
c <- res
}
func getApi3(c chan Result) {
res := Result{
orig: API3,
data: "some value",
}
c <- res
}
func main() {
results := make(chan Result, 3)
var wg sync.WaitGroup
wg.Add(3)
go func() {
defer wg.Done()
getApi1(results)
}()
go func() {
defer wg.Done()
getApi2(results)
}()
go func() {
defer wg.Done()
getApi3(results)
}()
go func() {
wg.Wait()
close(results)
}()
for res := range results {
fmt.Printf("%#v\n", res)
}
}
Possible output (the order of results isn't deterministic):
main.Result{orig:1, data:"some value"}
main.Result{orig:2, data:"some value"}
main.Result{orig:3, data:"some value"}
At any rate, I would not follow wic's suggestion; your problem is simply not a good use case for reflection. As Rob Pike puts it,
> Reflection is never clear. Another thing you see on Stack Overflow a lot is people trying to use reflect
and wondering why it doesn't work. It doesn't work because it's not for you... Very, very few people should be playing with reflection. It's a very powerful but very difficult-to-use feature. [...]
答案2
得分: 0
你可以使用chebyrash/promise
来实现这个(它在幕后使用了通道)。
var p1 = promise.Resolve(123)
var p2 = promise.Resolve("Hello, World")
var p3 = promise.Resolve([]string{"one", "two", "three"})
results, _ := promise.All(p1, p2, p3).Await()
fmt.Println(results)
// [123 Hello, World [one two three]]
如《Catching return values from goroutines》中所解释的:
这是Go创建者的设计选择。
有很多抽象/API可以表示异步I/O操作的值 -promise
、future
、async/await
、callback
、observable
等等。
我上面提到的项目是一个允许获取“promises”的组合示例。
要在本地实现相同的功能,你需要为每个预期结果创建一个通道。
可以参考《Use Go Channels as Promises and Async/Await》(来自Minh-Phuc Tran)和这个playground示例:
package main
import (
"fmt"
"math/rand"
"time"
)
func longRunningTask() <-chan int32 {
r := make(chan int32)
go func() {
defer close(r)
// 模拟一个工作负载。
time.Sleep(time.Second * 3)
r <- rand.Int31n(100)
}()
return r
}
func main() {
aCh, bCh, cCh := longRunningTask(), longRunningTask(), longRunningTask()
a, b, c := <-aCh, <-bCh, <-cCh
fmt.Println(a, b, c)
}
OP的有趣评论:
如果我需要同时运行500个API调用怎么办?我需要创建500个通道吗?
参考《Max number of goroutines》:500个goroutine(及其通道)不算什么。
如果我事先不知道API调用的数量怎么办?例如,我获取参数并在未知长度的数组上进行API调用?
那么,假设我们谈论的是大量的调用,你可以使用worker pool,就像这个更复杂的示例中所示。
你需要返回一个带有作业ID标记的结果,以便将其与期望特定结果的变量匹配起来。
英文:
You could use chebyrash/promise
for that (which uses channel behind the scene)
var p1 = promise.Resolve(123)
var p2 = promise.Resolve("Hello, World")
var p3 = promise.Resolve([]string{"one", "two", "three"})
results, _ := promise.All(p1, p2, p3).Await()
fmt.Println(results)
// [123 Hello, World [one two three]]
As explained in "Catching return values from goroutines":
> It is a design choice by Go creators.
There's a whole lot of abstractions/APIs to represent the value of async I/O operations - promise
, future
, async/await
, callback
, observable
, etc
The project I mention above is an example of composition which allows to get "promises".
To achieve the same natively, you would need one channel per expected result.
See as an example "Use Go Channels as Promises and Async/Await" (from Minh-Phuc Tran), and this playground example:
package main
import (
"fmt"
"math/rand"
"time"
)
func longRunningTask() <-chan int32 {
r := make(chan int32)
go func() {
defer close(r)
// Simulate a workload.
time.Sleep(time.Second * 3)
r <- rand.Int31n(100)
}()
return r
}
func main() {
aCh, bCh, cCh := longRunningTask(), longRunningTask(), longRunningTask()
a, b, c := <-aCh, <-bCh, <-cCh
fmt.Println(a, b, c)
}
Interesting comments from the OP:
> What if I need run 500 API calls concurrently? Should I make 500 channels?
See "Max number of goroutines": 500 goroutines (and their channels) is nothing
> What if I don't know in advance count of API calls? For example, I get params and get API calls over it array unknown length?
Then, assuming we are talking about a large number of calls, you can use a worker pool, as in this more sophisticated example.
You would need to return a result tagged with a job id in order to match it with the variable which expect a specific result.
答案3
得分: 0
另一种方法是使用goroutine而不是通道。这取决于你是否想在回调函数上进行一些同步。原始代码可以改写为:
go func(){
res := getApi1()
fmt.Println("Result 1: ", res)
}()
go func(){
res := getApi2()
fmt.Println("Result 2: ", res)
}()
go func(){
res := getApi3()
fmt.Println("Result 3: ", res)
}()
英文:
An other way to do is to use goroutine rather than channels. It depends whether you want to have some synchronization on the callbacks or not. This:
getApi1()
.then( res => console.debug("Result 1: ", res))
getApi2()
.then( res => console.debug("Result 2: ", res) )
getApi3()
.then( res => console.debug("Result 3: ", res) )
Would become
go func(){
res := getApi1()
fmt.Println("Result 1: ", res)
}
go func(){
res := getApi2()
fmt.Println("Result 2: ", res)
}
go func(){
res := getApi3()
fmt.Println("Result 3: ", res)
}
答案4
得分: -1
最佳实践是为每个goroutine创建一个通道,并使用包含所有通道的select
语句,但这样会导致代码重复。因此,你需要使用通用代码来实现这一点,Go语言有一个强大的包叫做reflection
。如果你是Go语言的新手,我建议你学习一下reflection
。你可以参考这个答案https://stackoverflow.com/a/19992525/10446155,代码如下:
package main
import (
"fmt"
"reflect"
)
func getApi1(c chan string) {
c <- "value from Api1"
}
func getApi2(c chan string) {
c <- "value from Api2"
}
func getApi3(c chan string) {
c <- "value from Api3"
}
// 包含所有通道的数组
var responses []chan string
func init() {
responses = make([]chan string, 3)
for i := 0; i < 3; i++ {
responses[i] = make(chan string)
}
}
func main() {
// 使用不同的通道调用每个Api函数
go getApi1(responses[0])
go getApi2(responses[1])
go getApi3(responses[2])
// 使用反射的通用select语句
cases := make([]reflect.SelectCase, len(responses))
for i, ch := range responses {
cases[i] = reflect.SelectCase{
Dir: reflect.SelectRecv,
Chan: reflect.ValueOf(ch),
}
}
count := len(cases)
for {
chosen, value, ok := reflect.Select(cases)
if !ok {
panic(fmt.Sprintf("channel at index %d is closed", chosen))
}
fmt.Printf("getApi%d returns: %s\n", chosen+1, value.String())
// 计算每个select的数量,并在全部完成后退出循环。如果不这样做,你的代码会陷入死锁状态
count -= 1
if count == 0 {
break
}
}
}
注意:我假设所有的Api函数都返回一个
string
类型的值。
英文:
The best practice is to have a channel for each goroutine and make a select
statement containing all channels, but it sounds like much-repeated code. So, you need to do that but with a generic code, Go have a powerful package called reflection
. If you are new to Go, I recommend you to learn reflection
. So, you can apply this answer https://stackoverflow.com/a/19992525/10446155 and the code looks like this:
package main
import (
"fmt"
"reflect"
)
func getApi1(c chan string) {
c <- "value from Api1"
}
func getApi2(c chan string) {
c <- "value from Api2"
}
func getApi3(c chan string) {
c <- "value from Api3"
}
// Array with all channels
var responses []chan string
func init() {
responses = make([]chan string, 3)
for i := 0; i < 3; i++ {
responses[i] = make(chan string)
}
}
func main() {
// Call each ApiFunction with different channel
go getApi1(responses[0])
go getApi2(responses[1])
go getApi3(responses[2])
// Generic select statement with reflection
cases := make([]reflect.SelectCase, len(responses))
for i, ch := range responses {
cases[i] = reflect.SelectCase{
Dir: reflect.SelectRecv,
Chan: reflect.ValueOf(ch),
}
}
count := len(cases)
for {
chosen, value, ok := reflect.Select(cases)
if !ok {
panic(fmt.Sprintf("channel at index %d is closed", chosen))
}
fmt.Printf("getApi%d returns: %s\n", chosen+1, value.String())
// Count each select, and break if all are made. If you don't
// do this, then your code enter in a deadlock condition
count -= 1
if count == 0 {
break
}
}
}
> Note: I suppose all ApiFunctionrs returns a string
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论