具有多种类型的协程和通道
Goroutines and Channels with Multiple Types
我对 Go 比较陌生,正在尝试找出同时从 REST API
中提取信息的最佳方法。目的是对 API
进行多个并发调用,每次调用 return 处理不同类型的数据。
我目前有:
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
但我想知道是否有更好的写法。
编辑:它仍然很难看,但将频道数量减少到一个:
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
调用,但由于所有函数都有不同的 return 类型,我得到了所有类型不匹配错误,并且您不能通过使用 func(api) interface{}
来伪造它,因为这只会造成 运行 时间恐慌。
您可能会发现创建 interface{}
类型的单个通道更容易,这样您就可以发送任何值。然后,在接收端,可以针对特定类型进行类型断言:
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:
}
你的方法很有意义,几乎没有免责声明。最后部分您按顺序从频道接收
services := <- c1
servicePlans := <- c2
serviceInstances := <- c3
serviceBindings := <- c4
但这会使您的代码同步,因为
go func() {
c2 <- GetServicePlans(s)
}()
不能写入c2,只要对方能读,在services := <- c1
执行之前不会。因此,尽管在 goroutines 中,代码仍将按顺序执行。使代码更加异步的最简单方法是至少提供缓冲通道
c1 := make(chan map[string]Service, 1) //buffered channel with capacity one
它允许 c1 <- GetServices(s) 执行它的工作并在通道中缓冲结果。
作为 chan interface{]
方法的替代方法,您可以考虑创建一个信封对象(下例中的 APIObject
)以指向您希望发送的真实值。它带有内存开销,但也增加了类型安全性。 (权衡这两种方法的评论和意见将不胜感激。)
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)
}
}
}
当我看到这个时,我认为我们可能将作业与完成混为一谈,从而为每种类型创建一个频道。
为每种类型创建一个用于分配的闭包和一个用于管理完成的通道可能会更简单。
示例:
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
Go 的作者预料到了这个用例,并提供了 sync.WaitGroup 这使得这个更清楚 sync.WaitGroup Docs 在它下面是花哨的原子操作,它取代了通道同步。
示例:
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
希望对您有所帮助。
我对 Go 比较陌生,正在尝试找出同时从 REST API
中提取信息的最佳方法。目的是对 API
进行多个并发调用,每次调用 return 处理不同类型的数据。
我目前有:
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
但我想知道是否有更好的写法。
编辑:它仍然很难看,但将频道数量减少到一个:
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
调用,但由于所有函数都有不同的 return 类型,我得到了所有类型不匹配错误,并且您不能通过使用 func(api) interface{}
来伪造它,因为这只会造成 运行 时间恐慌。
您可能会发现创建 interface{}
类型的单个通道更容易,这样您就可以发送任何值。然后,在接收端,可以针对特定类型进行类型断言:
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:
}
你的方法很有意义,几乎没有免责声明。最后部分您按顺序从频道接收
services := <- c1
servicePlans := <- c2
serviceInstances := <- c3
serviceBindings := <- c4
但这会使您的代码同步,因为
go func() {
c2 <- GetServicePlans(s)
}()
不能写入c2,只要对方能读,在services := <- c1
执行之前不会。因此,尽管在 goroutines 中,代码仍将按顺序执行。使代码更加异步的最简单方法是至少提供缓冲通道
c1 := make(chan map[string]Service, 1) //buffered channel with capacity one
它允许 c1 <- GetServices(s) 执行它的工作并在通道中缓冲结果。
作为 chan interface{]
方法的替代方法,您可以考虑创建一个信封对象(下例中的 APIObject
)以指向您希望发送的真实值。它带有内存开销,但也增加了类型安全性。 (权衡这两种方法的评论和意见将不胜感激。)
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)
}
}
}
当我看到这个时,我认为我们可能将作业与完成混为一谈,从而为每种类型创建一个频道。
为每种类型创建一个用于分配的闭包和一个用于管理完成的通道可能会更简单。
示例:
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
Go 的作者预料到了这个用例,并提供了 sync.WaitGroup 这使得这个更清楚 sync.WaitGroup Docs 在它下面是花哨的原子操作,它取代了通道同步。
示例:
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
希望对您有所帮助。