Goroutines and channels with several types

I'm relatively new to Go and trying to find a better way to retrieve information from the REST API at the same time. The goal is to make multiple simultaneous API calls, with each call returning a different data type.

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 is a better way to write this.

EDIT: It is 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 really like the way to do this, so I don’t need to hard code the loop, which needs to be run 6 times. I really tried to make a list of functions to run and do it in such a way as to remove duplicate go func calls, but since all functions have different types of returned data, I got all type mismatch errors and you cannot fake it using func(api) interface{} , as this only creates panic at runtime.

+5
source share
4 answers

When I see this, I think that we can combine the assignment with completion, thus creating one channel for each type.

It may be easier to create one closure for each type for assignment and one channel to control 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 foresaw this use case and provided sync.WaitGroup, which makes it a little clearer sync.WaitGroup Docs Under it, which replace 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 

Hope this helps.

+5
source

It may be easier for you to create a single channel of type interface{} , which allows you to send any value. Then on the receiving side you can execute a type statement 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: } 
+4
source

You approach, quite meaningfully, with a small disclaimer. In the end, your channel reception is consistent

 services := <- c1 servicePlans := <- c2 serviceInstances := <- c3 serviceBindings := <- c4 

but it makes your code synchronous because

 go func() { c2 <- GetServicePlans(s) }() 

cannot write to c2 until the opposite side is read, and it will not be executed until services := <- c1 . Therefore, despite the fact that in goroutines the code will be executed sequentially. The easiest way to make code more asynchronous is to at least provide buffered channels

 c1 := make(chan map[string]Service, 1) //buffered channel with capacity one 

which allow c1 <- GetServices (s) to do this job, and the result of the buffer in the channel.

0
source

As an alternative to the chan interface{] approach, you might consider creating a wrapper object ( APIObject in the example below) to indicate the true value you want to send. It comes with memory overhead, but also improves type safety. (Comments and opinions weighing the two approaches will be greatly 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) } } } 
0
source

Source: https://habr.com/ru/post/1247974/


All Articles