英文:
Goroutines and Channels with Multiple Types
问题
我相对于Go语言还比较新,正在尝试找出从REST API
并发获取信息的最佳方法。目的是对API
进行多个并发调用,每个调用返回不同类型的数据。
目前我有以下代码:
s := NewClient()
c1 := make(chan map[string]Service)
c2 := make(chan map[string]ServicePlan)
c3 := make(chan map[string]ServiceInstance)
c4 := make(chan map[string]ServiceBinding)
c5 := make(chan map[string]Organization)
c6 := make(chan map[string]Space)
go func() {
c1 <- GetServices(s)
}()
go func() {
c2 <- GetServicePlans(s)
}()
go func() {
c3 <- GetServiceInstances(s)
}()
go func() {
c4 <- GetServiceBindings(s)
}()
go func() {
c5 <- GetOrganizations(s)
}()
go func() {
c6 <- GetSpaces(s)
}()
services := <-c1
servicePlans := <-c2
serviceInstances := <-c3
serviceBindings := <-c4
orgs := <-c5
spaces := <-c6
// 后续将所有数据拼接在一起
但我想知道是否有更好的编写方式。
编辑:虽然还是有些丑陋,但将通道的数量减少到一个:
c := make(chan interface{})
var (
services map[string]Service
servicePlans map[string]ServicePlan
serviceInstances map[string]ServiceInstance
serviceBindings map[string]ServiceBinding
orgs map[string]Organization
spaces map[string]Space
)
go func() {
c <- GetServices(s)
}()
go func() {
c <- GetServicePlans(s)
}()
go func() {
c <- GetServiceInstances(s)
}()
go func() {
c <- GetServiceBindings(s)
}()
go func() {
c <- GetOrganizations(s)
}()
go func() {
c <- GetSpaces(s)
}()
for i := 0; i < 6; i++ {
v := <-c
switch v := v.(type) {
case map[string]Service:
services = v
case map[string]ServicePlan:
servicePlans = v
case map[string]ServiceInstance:
serviceInstances = v
case map[string]ServiceBinding:
serviceBindings = v
case map[string]Organization:
orgs = v
case map[string]Space:
spaces = v
}
}
我仍然希望有一种方法可以不必硬编码循环运行6次。我实际上尝试过创建一个要运行的函数列表,并以此方式去除重复的go func
调用,但由于所有函数具有不同的返回类型,我得到了所有类型不匹配的错误。而且你也不能通过使用func(api) interface{}
来伪造它,因为那只会创建一个运行时错误。
英文:
I'm relatively new to Go and trying to figure out the best way to concurrently pull information from a REST API
. The intent is to make multiple concurrent calls to an API
, with each call returning a different type of data.
I currently have:
s := NewClient()
c1 := make(chan map[string]Service)
c2 := make(chan map[string]ServicePlan)
c3 := make(chan map[string]ServiceInstance)
c4 := make(chan map[string]ServiceBinding)
c5 := make(chan map[string]Organization)
c6 := make(chan map[string]Space)
go func() {
c1 <- GetServices(s)
}()
go func() {
c2 <- GetServicePlans(s)
}()
go func() {
c3 <- GetServiceInstances(s)
}()
go func() {
c4 <- GetServiceBindings(s)
}()
go func() {
c5 <- GetOrganizations(s)
}()
go func() {
c6 <- GetSpaces(s)
}()
services := <- c1
servicePlans := <- c2
serviceInstances := <- c3
serviceBindings := <- c4
orgs := <- c5
spaces := <- c6
// stitch all the data together later
but I was wondering if there was a better way to write this.
EDIT: It's still ugly, but reduced the number of channels to one:
c := make(chan interface{})
var (
services map[string]Service
servicePlans map[string]ServicePlan
serviceInstances map[string]ServiceInstance
serviceBindings map[string]ServiceBinding
orgs map[string]Organization
spaces map[string]Space
)
go func() {
c <- GetServices(s)
}()
go func() {
c <- GetServicePlans(s)
}()
go func() {
c <- GetServiceInstances(s)
}()
go func() {
c <- GetServiceBindings(s)
}()
go func() {
c <- GetOrganizations(s)
}()
go func() {
c <- GetSpaces(s)
}()
for i := 0; i < 6; i++ {
v := <-c
switch v := v.(type) {
case map[string]Service:
services = v
case map[string]ServicePlan:
servicePlans = v
case map[string]ServiceInstance:
serviceInstances = v
case map[string]ServiceBinding:
serviceBindings = v
case map[string]Organization:
orgs = v
case map[string]Space:
spaces = v
}
}
I would still really like a way to do this so I don't have to hard-code that the loop needs to run 6 times. I actually tried make a list of functions to run and doing it that way to remove the repetitive go func
calls, but since all the functions have different return types, I got all type mismatch errors, and you can't fake it by using func(api) interface{}
either as that just creates a runtime panic.
答案1
得分: 10
当我看到这个代码时,我认为我们可能将赋值和完成混淆在一起,从而为每种类型创建一个通道。
为了简化,可以为每种类型创建一个闭包来进行赋值,并创建一个单一的通道来管理完成。
示例:
s := NewClient()
c := make(chan bool)
// 这里我不太清楚类型
var services services
var servicePlans servicePlans
var serviceInstances serviceInstances
var serviceBindings serviceInstances
var orgs orgs
var spaces spaces
go func() {
service = GetServices(s)
c <- true
}()
go func() {
servicePlans = GetServicePlans(s)
c <- true
}()
go func() {
serviceInstances = GetServiceInstances(s)
c <- true
}()
go func() {
serviceBindings = GetServiceBindings(s)
c <- true
}()
go func() {
orgs = GetOrganizations(s)
c <- true
}()
go func() {
spaces = GetSpaces(s)
c <- true
}()
for i = 0; i < 6; i++ {
<-c
}
// 稍后将所有数据拼接在一起
Go语言的作者预见到了这种用例,并提供了sync.WaitGroup来使代码更清晰一些。sync.WaitGroup文档 在底层使用了一些高级的原子操作来替代通道同步。
示例:
s := NewClient()
// 再次,这里我不太确定类型
var services services
var servicePlans servicePlans
var serviceInstances serviceInstances
var serviceBindings serviceInstances
var orgs orgs
var spaces spaces
var wg sync.WaitGroup
wg.Add(6)
go func() {
service = GetServices(s)
wg.Done()
}()
go func() {
servicePlans = GetServicePlans(s)
wg.Done()
}()
go func() {
serviceInstances = GetServiceInstances(s)
wg.Done()
}()
go func() {
serviceBindings = GetServiceBindings(s)
wg.Done()
}()
go func() {
orgs = GetOrganizations(s)
wg.Done()
}()
go func() {
spaces = GetSpaces(s)
wg.Done()
}()
// 阻塞直到所有六个任务完成
wg.Wait()
// 稍后将所有数据拼接在一起
希望对你有所帮助。
英文:
When I see this, I think we may be conflating assignment with completion, thus creating one channel per type.
It may be simpler to create one closure per type for assignment and a single channel to manage completion.
example:
s := NewClient()
c := make(chan bool)
// I don't really know the types here
var services services
var servicePlans servicePlans
var serviceInstances serviceInstances
var serviceBindings serviceInstances
var orgs orgs
var spaces spaces
go func() {
service = GetServices(s)
c <- true
}()
go func() {
servicePlans = GetServicePlans(s)
c <- true
}()
go func() {
serviceInstances = GetServiceInstances(s)
c <- true
}()
go func() {
serviceBindings = GetServiceBindings(s)
c <- true
}()
go func() {
orgs = GetOrganizations(s)
c <- true
}()
go func() {
spaces = GetSpaces(s)
c <- true
}()
for i = 0; i < 6; i++ {
<-c
}
// stitch all the data together later
The Go authors anticipated this use-case, and provide the sync.WaitGroup which makes this a little clearer sync.WaitGroup Docs Underneath it is fancy atomic operations which replace the channel synchronization.
example:
s := NewClient()
// again, not sure of the types here
var services services
var servicePlans servicePlans
var serviceInstances serviceInstances
var serviceBindings serviceInstances
var orgs orgs
var spaces spaces
var wg sync.WaitGroup
wg.Add(6)
go func() {
service = GetServices(s)
wg.Done()
}()
go func() {
servicePlans = GetServicePlans(s)
wg.Done()
}()
go func() {
serviceInstances = GetServiceInstances(s)
wg.Done()
}()
go func() {
serviceBindings = GetServiceBindings(s)
wg.Done()
}()
go func() {
orgs = GetOrganizations(s)
wg.Done()
}()
go func() {
spaces = GetSpaces(s)
wg.Done()
}()
// blocks until all six complete
wg.Wait()
// stitch all the data together later
I hope this is helps.
答案2
得分: 7
你可以创建一个类型为interface{}
的单通道,这样可以发送任何值。然后,在接收端,你可以进行类型断言来获取特定类型的值:
c := make(chan interface{})
/* 发送: */
c <- 42
c <- "test"
c <- &ServicePlan{}
/* 接收 */
something := <-c
switch v := something.(type) {
case int: // 将v作为int类型处理
// 处理int类型的情况
case string: // 将v作为string类型处理
// 处理string类型的情况
case *ServicePlan: // 将v作为ServicePlan实例指针处理
// 处理ServicePlan类型的情况
default:
// 处理其他类型的情况
}
这样,你就可以通过单通道发送和接收不同类型的值了。
英文:
You might find it easier to create a single channel of type interface{}
which will allow you to send any value. Then, on the receiving end, you can perform a type assertion for a specific type:
c := make(chan interface{})
/* Sending: */
c <- 42
c <- "test"
c <- &ServicePlan{}
/* Receiving */
something := <-c
switch v := something.(type) {
case int: // do something with v as an int
case string: // do something with v as a string
case *ServicePlan: // do something with v as an instance pointer
default:
}
答案3
得分: 0
你的方法非常有意义,但是在最后部分,你按顺序从通道接收数据:
services := <-c1
servicePlans := <-c2
serviceInstances := <-c3
serviceBindings := <-c4
这使得你的代码变成了同步执行,因为在go func() {...}
中的代码无法写入c2
,除非对面的代码读取,而对面的代码在执行services := <-c1
之前不会读取。所以尽管在goroutine中,代码仍然按顺序执行。让代码更加异步的最简单方法是至少提供带有缓冲区的通道:
c1 := make(chan map[string]Service, 1) //容量为1的缓冲通道
这样,c1 <- GetServices(s)
可以完成它的工作并将结果缓冲在通道中。
英文:
You approach is quite meaningful with little disclaimer. In the end part your receive from channels sequentially
services := <- c1
servicePlans := <- c2
serviceInstances := <- c3
serviceBindings := <- c4
but this make your code synchronous, because
go func() {
c2 <- GetServicePlans(s)
}()
can't write to c2 as long as opposite side would read, and it would not before services := <- c1
execute. So despite been in goroutines code will execute sequentially. Easiest way to make code more async is at least to provide buffered channels
c1 := make(chan map[string]Service, 1) //buffered channel with capacity one
which allow c1 <- GetServices(s) do it's work and buffer result in channel.
答案4
得分: 0
作为替代chan interface{}
方法的一种方法,您可以考虑创建一个包装对象(在下面的示例中为APIObject
),指向您希望发送的真实值。这会增加内存开销,但也增加了类型安全性。(非常感谢对这两种方法进行权衡的评论和意见。)
package main
import (
"fmt"
)
type Service struct{ Name string }
type ServicePlan struct{ Name string }
type Organization struct{ Name string }
type APIObject struct {
Service *Service
ServicePlan *ServicePlan
Organization *Organization
}
func main() {
objs := make(chan APIObject)
go func() {
objs <- APIObject{Service: &Service{"Service-1"}}
objs <- APIObject{ServicePlan: &ServicePlan{"ServicePlan-1"}}
objs <- APIObject{Organization: &Organization{"Organization-1"}}
close(objs)
}()
for obj := range objs {
if obj.Service != nil {
fmt.Printf("Service: %v\n", obj.Service)
}
if obj.ServicePlan != nil {
fmt.Printf("ServicePlan: %v\n", obj.ServicePlan)
}
if obj.Organization != nil {
fmt.Printf("Organization: %v\n", obj.Organization)
}
}
}
链接:https://play.golang.org/p/1KIZ7Qfg43
英文:
As an alternative to the chan interface{]
approach, you might consider creating an envelope object (APIObject
in the example below) to point to the true value that you wish to send. It comes with memory overheads, but also increased type-safety. (Comments and opinions weighing the two approach would be much appreciated.)
https://play.golang.org/p/1KIZ7Qfg43
package main
import (
"fmt"
)
type Service struct{ Name string }
type ServicePlan struct{ Name string }
type Organization struct{ Name string }
type APIObject struct {
Service *Service
ServicePlan *ServicePlan
Organization *Organization
}
func main() {
objs := make(chan APIObject)
go func() {
objs <- APIObject{Service: &Service{"Service-1"}}
objs <- APIObject{ServicePlan: &ServicePlan{"ServicePlan-1"}}
objs <- APIObject{Organization: &Organization{"Organization-1"}}
close(objs)
}()
for obj := range objs {
if obj.Service != nil {
fmt.Printf("Service: %v\n", obj.Service)
}
if obj.ServicePlan != nil {
fmt.Printf("ServicePlan: %v\n", obj.ServicePlan)
}
if obj.Organization != nil {
fmt.Printf("Organization: %v\n", obj.Organization)
}
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论