Goroutines和多种类型的频道

时间:2016-04-26 16:09:14

标签: go channel goroutine

我对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
// 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调用,但由于所有函数都有不同的返回类型,我得到了所有类型不匹配错误,你不能这样做使用func(api) interface{}伪造它,因为它只会造成运行时恐慌。

4 个答案:

答案 0 :(得分:7)

当我看到这一点时,我认为我们可能会将分配与完成混为一谈,从而为每种类型创建一个通道。

为每个类型创建一个用于分配的闭包和一个用于管理完成的通道可能更简单。

示例:

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

我希望这有帮助。

答案 1 :(得分:4)

您可能会发现创建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:
}

答案 2 :(得分:0)

你的方法很有意义,几乎没有免责声明。在最后部分,您按顺序从频道接收

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&lt; - GetServices(s)在通道中执行它的工作和缓冲区结果。

答案 3 :(得分:0)

作为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)
        }
    }
}