Goroutines and Channels with Multiple Types

Issue

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.

Solution

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.

Answered By – foo

Answer Checked By – Katrina (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.