Search code examples
functiongoconcurrencyreturn-value

Reading multiple return values from goroutines


I'm trying to write wc(1) in Go, and I'm experimenting with goroutines in an effort to count large number of input files more efficiently. My code works fine as it is, but I have a hard time implementing a method to summarize the statistics for all the go routines. How can I pass the function variables nl, nw, nc to main and summarize them there once all the go routines have completed their work?

package main

import (
    "bufio"
    "fmt"
    "os"
    "strings"
)

func main() {
    ch := make(chan string)

    for _, arg := range os.Args[1:] {
        go wc(arg, ch)
    }
    for range os.Args[1:] {
        fmt.Println(<-ch)
    }
    // Todo: summarize results...
}

func wc(arg string, ch chan<- string) {
    nl, nw, nc := 0, 0, 0

    file, err := os.Open(arg)
    if err != nil {
        fmt.Println("Can't open file: %v", err)
    }
    defer file.Close()

    scan := bufio.NewScanner(file)
    for scan.Scan() {
        nl++

        line := scan.Text()
        words := bufio.NewScanner(strings.NewReader(line))
        words.Split(bufio.ScanWords)
        for words.Scan() {
            nw++
        }

        runes := bufio.NewReader(strings.NewReader(line))
        for {
            if _, _, err := runes.ReadRune(); err != nil {
                break
            } else {
                nc++
            }
        }
    }

    ch <- fmt.Sprintf("%8d%8d%8d", nl, nw, nc+nl)
}

Solution

  • You're close to the answer! I suggest a quick refacto though to return a Result object with the numbers, that will allow to easily add them at the end (instead of using strings). So you can use a chan Result instead of chan string.

    Basically, you can introduce a totalResult variable, and when iterating on all results, just add the results for nl, nc and nw into that total variable.

    package main
    
    import (
        "fmt"
        "math/rand"
    )
    
    // define a struct to hold the result
    type Result struct {
        nl int
        nw int
        nc int
    }
    
    // this is to be able to use fmt.Println(result)
    func (r Result) String() string {
        return fmt.Sprintf("%8d%8d%8d", r.nl, r.nw, r.nc+r.nl)
    }
    
    func main() {
        ch := make(chan Result)
    
        for _, arg := range os.Args[1:] {
            go wc(arg, ch)
        }
    
        totalResult := Result{}
    
        for range os.Args[1:] {
            result := <-ch
            fmt.Println(result) // just for debugging
    
            // sum everything
            totalResult.nl += result.nl
            totalResult.nw += result.nw
            totalResult.nc += result.nc
        }
    
        fmt.Println("Total result:")
        fmt.Println(totalResult)
    }
    
    func wc(arg string, ch chan<- Result) {
        nl, nw, nc := 0, 0, 0
    
        // your logic to compute nl, nw, nc goes here
    
        ch <- Result{nl: nl, nw: nw, nc: nc + nl}
    }
    

    You should get something like this (with 3 files):

          37      50    4753
          19     106     821
          47     255    3806
    Total result:
         103     411    9380