Below is a struct of type Stuff. It has three ints. A Number
, its Double
and its Power
. Let's pretend that calculating the double and power of a given list of ints is an expensive computation.
type Stuff struct {
Number int
Double int
Power int
}
func main() {
nums := []int{2, 3, 4} // given numbers
stuff := []Stuff{} // struct of stuff with transformed ints
double := make(chan int)
power := make(chan int)
for _, i := range nums {
go doubleNumber(i, double)
go powerNumber(i, power)
}
// How do I get the values back in the right order?
fmt.Println(stuff)
}
func doubleNumber(i int, c chan int) {
c <- i + i
}
func powerNumber(i int, c chan int) {
c <- i * i
}
The result of fmt.Println(stuff)
should be the same as if stuff was initialized like:
stuff := []Stuff{
{Number: 2, Double: 4, Power: 4}
{Number: 3, Double: 6, Power: 9}
{Number: 4, Double: 8, Power: 16}
}
I know I can use <- double
and <- power
to collect values from the channels, but I don't know what double / powers belong to what numbers.
Goroutines run concurrently, independently, so without explicit synchronization you can't predict execution and completion order. So as it is, you can't pair returned numbers with the input numbers.
You can either return more data (e.g. the input number and the output, wrapped in a struct for example), or pass pointers to the worker functions (launched as new goroutines), e.g.
*Stuff
and have the goroutines fill the calculated data in theStuff
itself.Returning more data
I will use a channel type
chan Pair
wherePair
is:So calculation will look like this:
And I will use a
map[int]*Stuff
because collectable data comes from multiple channels (double
andpower
), and I want to find the appropriateStuff
easily and fast (pointer is required so I can also modify it "in the map").So the main function:
Output (try it on the Go Playground):
Using pointers
Since now we're passing
*Stuff
values, we can "pre-fill" the input number in theStuff
itself.But care must be taken, you can only read/write values with proper synchronization. Easiest is to wait for all "worker" goroutines to finish their jobs.
Output (try it on the Go Playground):
Personally, I would use a
chan Stuff
to pass the results back on, then spin up goroutines computing a fullStuff
and pass it back. If you need the various part of a singleStuff
computed concurrently, you can spawn goroutines from each goroutine, using dedicated channels. Once you've collected all the results, you can then (optionally) sort the slice with the accumulated values.Example of what I mean below (you could, in principle, use a
sync.WaitGroup
to coordinate things, but if the input count is known, you don't strictly speaking need it).