i'm new to Go and am wondering about some pretty basic problem that i can't figure out clearly.
Just for the exercize (an abstraction of a real need), i need to:
- initialize a slice of string with a number of elements fixed by the constant
ITERATIONS - iterate over this slice and run a goroutine for each element
- each goroutine will take a certain amount of time to process the element (a random second duration)
- once the job is finished, i want the goroutine to push the result to a channel
- then i need to catch all the results from this channel (in a function called from the main goroutine), append them to the final slice and then, once its finished
- print the length of the final slice and add some basic time tracking
Below is a code that works.
No surprise, the total amount of program time is always more or less equals to const MAX_SEC_SLEEP's value, as all the processing goroutine do they work in parallel.
But what about:
- the receive part:
do i really need to wrap my select statement in a for loop, iterating the exact amount of ITERATIONS , to have the exact same number of receivers than the number of goroutines that will end to the channel ? Is it the only way to avoid deadlock here ? And what if for some reason, one of the goroutine fails ?
I can't find a way to have a simple for (ever) loop wrapping the select, with two cases (the one receiving from the results channel and another one like case <-done that wouldreturn from the function). Would it be a better pattern ?
Or would it be better to iterate over the channel and detect if it is closes from somewhere ?
- the send part
Should i close somewhere the channel, after all the iterations ? but i would surely close it before at least one of the gouroutine finishes, ending in a panic error (trying to send to a closed channel)
If i were to plug a done <- true pattern, would it be here ?
- Wait groups
i did not really try waitgroups, ad i need a way to catch all 'return' values from the goroutines and append them to the final slice; and i did not find a proper way to return from a goroutine except by using channels.
- Misc
Should i pass channels in func arguments or let them global to the program as it is ?
- The (bad) code
package main
import (
"fmt"
"log"
"math/rand"
"time"
)
const ITERATIONS = 200
var (
results chan string
initial []string
formatted []string
)
func main() {
defer timeTrack(time.Now(), "program")
format() //run format goroutines
receive() //receive formatted strings
log.Printf("final slice contains %d/%d elements", len(formatted), len(initial))
}
//gets all results from channel and appends them to formatted slice
func receive() {
for i := 0; i < ITERATIONS; i++ {
select {
case result := <-results:
formatted = append(formatted, result)
}
}
}
//loops over initial slice and runs a goroutine per element
//that does some formatting operation and then pushes result to channel
func format() {
for i := 0; i < ITERATIONS; i++ {
go func(i int) {
//simulate some formatting code that can take a while
sleep := time.Duration(rand.Intn(10)) * time.Second
time.Sleep(sleep)
//append formatted string to result chan
results <- fmt.Sprintf("%s formatted", initial[i])
}(i)
}
}
//initialize chans and inital slice
func init() {
results = make(chan string, ITERATIONS)
for i := 0; i < ITERATIONS; i++ {
initial = append(initial, fmt.Sprintf("string #%d", i))
}
}
func timeTrack(start time.Time, name string) {
elapsed := time.Since(start)
log.Printf("%s took %s", name, elapsed)
}