Goroutines
Every method of Bar is safe to call from many goroutines at once. Workers can share one bar and advance it as
each finishes a piece of work, with no lock of your own.
// Command parallel checksums files on four goroutines that share one bar.//// go run ./examples/parallel <file>...package main
import ( "crypto/sha256" "encoding/hex" "errors" "fmt" "io" "log" "os" "sync"
"github.com/vardius/progress-go")
// checksum returns the SHA-256 of each file, hashed by up to workers goroutines at once.// Each advances the bar when it finishes a file.func checksum(out io.Writer, paths []string, workers int) ([]string, error) { sums := make([]string, len(paths)) errs := make([]error, len(paths)) bar := progress.New(int64(len(paths)), progress.Options{Output: out}) bar.Start()
next := make(chan int) var wg sync.WaitGroup for range workers { wg.Go(func() { for i := range next { sums[i], errs[i] = sum(paths[i]) bar.Advance(1) } }) } for i := range paths { next <- i } close(next) wg.Wait()
return sums, errors.Join(append(errs, bar.Stop())...)}
func sum(path string) (string, error) { f, err := os.Open(path) if err != nil { return "", err } defer f.Close()
h := sha256.New() if _, err := io.Copy(h, f); err != nil { return "", fmt.Errorf("hash %s: %w", path, err) } return hex.EncodeToString(h.Sum(nil)), nil}
func main() { log.SetFlags(0) sums, err := checksum(os.Stderr, os.Args[1:], 4) for i, s := range sums { if s != "" { fmt.Printf("%s %s\n", s, os.Args[i+1]) } } if err != nil { log.Fatal(err) }}The program draws the bar to standard error, so the checksums on standard output stay clean for a pipe.
What Advance costs
Section titled “What Advance costs”Advance is one atomic add on the bar’s counter. On one goroutine it takes about 2 ns and never allocates.
When many goroutines advance the same bar at once, they take turns on that one counter, and each call costs
more: see Benchmarks. That is still far below the cost of any real piece of work, but if each
of your units is tiny, count them locally and advance once per batch:
for i, row := range rows { insert(row) if (i+1)%1000 == 0 { bar.Advance(1000) }}bar.Advance(int64(len(rows) % 1000))The redraw timer
Section titled “The redraw timer”On a terminal, Start draws the bar and sets a timer. Each time it fires, the bar reads the count, draws a frame,
and sets the timer again, 100 ms on. Advance takes no part, so however many goroutines call it, and however
fast, the terminal gets ten frames a second.
Start, the redraws and Stop share one lock, which also orders the writes. Stop stops the timer and draws the
last frame under that lock, so once Stop returns, the bar never writes again, even if a redraw was due.