I'm still experiencing how to make best use of channels. I have 5 outbound service calls (take ~2 min to return), and each gives me a pair of return values. e.g. func serviceCall()(T, error)
I want to make them concurrent, but I find the code very lengthy.
Basically, I have to create 5 channels, 5 structs to hold the return value.
I wrote a simple example to express the scenario, I want to know what is the pattern for this scenario, how I can make this code better.
package main
import (
"fmt"
"math/rand"
"time"
"log"
)
// goal: run multiple functions concurrently and process the results.
func main() {
now := time.Now()
// method1
type res1 struct {
news string
err error
}
type res2 struct {
n int
err error
}
ch1 := make(chan *res1)
ch2 := make(chan *res2)
go func() {
var res res1
res.news, res.err = news1()
ch1 <- &res
}()
go func() {
var res res2
res.n, res.err = news2()
ch2 <- &res
}()
one := <-ch1
if one.err != nil {
log.Fatal(one.err)
}
fmt.Println("news1: ", one.news)
two := <-ch2
if two.err != nil {
log.Fatal(two.err)
}
fmt.Println("news2: ", two.n)
fmt.Println("time elapsed: ", time.Since(now))
}
// first sleeps 5 seconds and returns random number or error.
func news1() (string, error) {
time.Sleep(time.Second * 5)
return "new1 is here.", nil
}
// second sleeps random seconds and returns random number or error.
func news2() (int, error) {
n := rand.Intn(20)
time.Sleep(time.Duration(n) * time.Second)
return n, nil
}
As far as I can tell your requirement of 5 service calls outbound is not reflected in the example code that you have written. The example code seems like it will work (I didn't run it to check) but it doesn't cover your use case.
I would recommend starting smaller with goroutines. This site and linked youtube talk helped me get started understanding how to manage goroutines.
https://talks.golang.org/2012/concurrency.slide#1
This video also helped me immensely when I started getting lost in the above talk:
There is no single pattern for this. There are different ways to accomplish it. The simplest is probably with a waitgroup, which requires no channels at all. The pattern for this would look like this:
func dostuff() {
var result1 int
var result2 string
var resultN SomeStruct
var err1, err2, errN error
wg := sync.WaitGroup{}
wg.Add(1)
go func() {
defer wg.Done()
result1, err1 = doStuff1()
}
wg.Add(1)
go func() {
defer wg.Done()
result2, err2 = doStuff2()
}
// repeat as often as you like
wg.Add(1)
go func() {
defer wg.Done()
resultN, errN = doStuffN()
}
wg.Wait()
// handle results and errors
}
The most obvious shortcoming of this approach is that you don't have the flexibility to abort any outstanding operations in case of an error. This may or may not matter to you.