go – Orchestrating an unknown number of goroutines


I recently wrote a piece of code to parallelise some code with goroutines and I was wondering if I could get some feedback. It looks overly complex… but I’m not sure how I could make it simpler.

It’s self contained, so you can just copy-paste it into the Go Playground and run it. Here’s a link if anybody’s interested: https://play.golang.org/p/Dyg4kZXCS9b

package main

import (
    "fmt"
    "sync"
)

func main() {
    fmt.Println("Hello World")
    input := ()string{"foo", "bar", "baz"}

    products, err := findProducts(input)
    if err != nil {
        panic(err)
    }

    fmt.Printf("n %d", len(products))
}

type productInfo struct{}

func findProduct(productCode string) (*productInfo, error) {
    return &productInfo{}, nil
}

func findProducts(productCodes ()string) (()*productInfo, error) {
    wg := sync.WaitGroup{}
    wg.Add(len(productCodes))

    output := make(chan ()*productInfo)
    input := make(chan *productInfo)

    errInput := make(chan error)
    errOutput := make(chan ()error)

    // consumer goroutine
    go func(input <-chan *productInfo, output chan<- ()*productInfo, errInput <-chan error, errOutput chan<- ()error, wg *sync.WaitGroup) {
        var results ()*productInfo
        var errors ()error

        for {
            select {
            case result, ok := <-input:
                if !ok {
                    input = nil
                    break
                }

                wg.Done()
                fmt.Println("input received through channel: appending productInfo")
                results = append(results, result)
            case err, ok := <-errInput:
                if !ok {
                    errInput = nil
                    break
                }

                wg.Done()
                fmt.Println("error received through channel: appending err")
                errors = append(errors, err)
            }

            if input == nil && errInput == nil {
                break
            }
        }

        output <- results
        errOutput <- errors
    }(input, output, errInput, errOutput, &wg)

    for _, code := range productCodes {
        go func(code string, input chan<- *productInfo, errInput chan<- error) {
            p, err := findProduct(code)
            if err != nil {
                fmt.Println("sending error through channel")
                errInput <- err
            } else {
                fmt.Println("sending input through channel")
                input <- p
            }
        }(code, input, errInput)
    }

    wg.Wait()
    close(input)
    close(errInput)

    products := <-output
    errs := <-errOutput

    // Just return the first one for simplicity
    if len(errs) > 0 {
        return nil, errs(0)
    }

    return products, nil
}
```