I have a program that starts a bunch of goroutines, feeds them data and when there is no more data, waits for the goroutines to finish. I use sync.WaitGroup. I am unsure if I am using correctly, in an idiomatic way or if there is a better solution.
Background
My real code reads a log file of disk-space and numbers of files backed up etc for several servers. The records are intermingled. From this data it produces a web-page report per server with graphs etc. It works but I believe I can more cleanly separate the functions if I use concurrency. This is a test program before I go ahead and refactor.
Code (72 lines including data)
package main
import (
"fmt"
"strconv"
"strings"
"sync"
)
func main() {
p := newPool()
defer p.Close()
for _, line := range strings.Split(logData, "\n") {
f := strings.Fields(line)
yr, _ := strconv.Atoi(f[0])
sub := f[1]
amt, _ := strconv.Atoi(f[2])
r := logRec{yr, sub, amt}
p.Report(r)
}
}
type logRec struct {
when int
subject string
amount int
}
type pool struct {
reporters map[string]chan logRec
wg *sync.WaitGroup
}
func newPool() pool {
rs := make(map[string]chan logRec)
var wg sync.WaitGroup
return pool{reporters: rs, wg: &wg}
}
func (p *pool) Report(rec logRec) {
if _, ok := p.reporters[rec.subject]; !ok {
c := make(chan logRec)
p.wg.Add(1)
go func() {
defer p.wg.Done()
tot := 0
for r := range c {
tot += r.amount
}
fmt.Printf("Total for %s is %d\n", rec.subject, tot)
}()
p.reporters[rec.subject] = c
}
p.reporters[rec.subject] <- rec
}
func (p pool) Close() {
for _, c := range p.reporters {
close(c)
}
p.wg.Wait()
}
const logData = `2018 apples 9
2018 oranges 5
2019 apples 27
2019 lemons 13
2019 oranges 2
2020 oranges 1
2020 apples 16
2020 lemons 3`
Output
Total for oranges is 8
Total for lemons is 16
Total for apples is 52
Question:
Can I improve this? Is there a better way to wait or a more idiomatic use of WaitGroups?