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.
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.
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:
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With