diff --git a/README.md b/README.md index ef50fc9..e7d0bc0 100644 --- a/README.md +++ b/README.md @@ -77,10 +77,10 @@ own goroutine, therefore adding multiple bars is easy and safe: ```go - p := mpb.New() + var wg sync.WaitGroup + p := mpb.New(mpb.WithWaitGroup(&wg)) total := 100 numBars := 3 - var wg sync.WaitGroup wg.Add(numBars) for i := 0; i < numBars; i++ { @@ -97,13 +97,14 @@ go func() { defer wg.Done() for i := 0; i < total; i++ { - time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond) - bar.Incr(1) + time.Sleep(time.Duration(rand.Intn(10)+1) * time.Second / 100) + bar.Increment() } }() } - wg.Wait() // Wait for goroutines to finish - p.Stop() // Stop mpb's rendering goroutine + // Wait for incr loop goroutines to finish, + // and shutdown mpb's rendering goroutine + p.Stop() ``` ![simple.gif](examples/gifs/simple.gif) diff --git a/example_test.go b/example_test.go index 70eff0c..be3a9ce 100644 --- a/example_test.go +++ b/example_test.go @@ -39,8 +39,8 @@ ) for i := 0; i < total; i++ { - time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond) - bar.Incr(1) // increment progress bar + time.Sleep(time.Duration(rand.Intn(10)+1) * time.Second / 100) + bar.Increment() } p.Stop() @@ -51,7 +51,7 @@ bar := p.AddBar(100, mpb.AppendDecorators(decor.Percentage(5, 0))) for bar.InProgress() { - time.Sleep(time.Millisecond * 20) - bar.Incr(1) + time.Sleep(time.Duration(rand.Intn(10)+1) * time.Second / 100) + bar.Increment() } } diff --git a/examples/cancel/main.go b/examples/cancel/main.go index fa4a20c..296c974 100644 --- a/examples/cancel/main.go +++ b/examples/cancel/main.go @@ -22,9 +22,11 @@ ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() - p := mpb.New(mpb.WithContext(ctx)) - var wg sync.WaitGroup + p := mpb.New( + mpb.WithWaitGroup(&wg), + mpb.WithContext(ctx), + ) total := 100 numBars := 3 wg.Add(numBars) @@ -56,7 +58,6 @@ }() } - wg.Wait() p.Stop() fmt.Println("stop") } diff --git a/examples/io/multiple/main.go b/examples/io/multiple/main.go index 6b75696..6645969 100644 --- a/examples/io/multiple/main.go +++ b/examples/io/multiple/main.go @@ -20,7 +20,7 @@ url2 := "https://homebrew.bintray.com/bottles/libtiff-4.0.7.sierra.bottle.tar.gz" var wg sync.WaitGroup - p := mpb.New(mpb.WithWidth(64)) + p := mpb.New(mpb.WithWaitGroup(&wg)) for i, url := range [...]string{url1, url2} { wg.Add(1) @@ -28,7 +28,6 @@ go download(&wg, p, name, url) } - wg.Wait() p.Stop() fmt.Println("Finished") } diff --git a/examples/prependETA/main.go b/examples/prependETA/main.go index 31b2f57..e128709 100644 --- a/examples/prependETA/main.go +++ b/examples/prependETA/main.go @@ -16,11 +16,10 @@ func main() { - p := mpb.New(mpb.WithWidth(64)) - + var wg sync.WaitGroup + p := mpb.New(mpb.WithWaitGroup(&wg)) total := 100 numBars := 3 - var wg sync.WaitGroup wg.Add(numBars) for i := 0; i < numBars; i++ { @@ -48,7 +47,6 @@ }() } - wg.Wait() p.Stop() fmt.Println("stop") } diff --git a/examples/prependElapsed/main.go b/examples/prependElapsed/main.go index a22e3e9..89370e3 100644 --- a/examples/prependElapsed/main.go +++ b/examples/prependElapsed/main.go @@ -16,11 +16,10 @@ func main() { - p := mpb.New(mpb.WithWidth(64)) - + var wg sync.WaitGroup + p := mpb.New(mpb.WithWaitGroup(&wg)) total := 100 numBars := 3 - var wg sync.WaitGroup wg.Add(numBars) for i := 0; i < numBars; i++ { @@ -48,7 +47,6 @@ }() } - wg.Wait() p.Stop() fmt.Println("stop") } diff --git a/examples/remove/main.go b/examples/remove/main.go index 8ab8399..f11edcd 100644 --- a/examples/remove/main.go +++ b/examples/remove/main.go @@ -16,11 +16,10 @@ func main() { - p := mpb.New(mpb.WithWidth(64)) - + var wg sync.WaitGroup + p := mpb.New(mpb.WithWaitGroup(&wg)) total := 100 numBars := 3 - var wg sync.WaitGroup wg.Add(numBars) for i := 0; i < numBars; i++ { @@ -52,7 +51,6 @@ }() } - wg.Wait() p.Stop() fmt.Println("stop") } diff --git a/examples/simple/main.go b/examples/simple/main.go index dd4f645..0d472f2 100644 --- a/examples/simple/main.go +++ b/examples/simple/main.go @@ -11,10 +11,10 @@ ) func main() { - p := mpb.New() + var wg sync.WaitGroup + p := mpb.New(mpb.WithWaitGroup(&wg)) total := 100 numBars := 3 - var wg sync.WaitGroup wg.Add(numBars) for i := 0; i < numBars; i++ { @@ -31,11 +31,12 @@ go func() { defer wg.Done() for i := 0; i < total; i++ { - time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond) - bar.Incr(1) + time.Sleep(time.Duration(rand.Intn(10)+1) * time.Second / 100) + bar.Increment() } }() } - wg.Wait() // Wait for goroutines to finish - p.Stop() // Stop mpb's rendering goroutine + // Wait for incr loop goroutines to finish, + // and shutdown mpb's rendering goroutine + p.Stop() } diff --git a/examples/singleBar/main.go b/examples/singleBar/main.go index 7b996b7..da0c5f3 100644 --- a/examples/singleBar/main.go +++ b/examples/singleBar/main.go @@ -39,8 +39,8 @@ ) for i := 0; i < total; i++ { - time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond) - bar.Incr(1) // increment progress bar + time.Sleep(time.Duration(rand.Intn(10)+1) * time.Second / 100) + bar.Increment() } p.Stop() diff --git a/examples/sort/main.go b/examples/sort/main.go index 16527c7..e0e6d07 100644 --- a/examples/sort/main.go +++ b/examples/sort/main.go @@ -35,11 +35,13 @@ func main() { - p := mpb.New(mpb.WithWidth(64), mpb.WithBeforeRenderFunc(sortByProgressFunc())) - + var wg sync.WaitGroup + p := mpb.New( + mpb.WithWaitGroup(&wg), + mpb.WithBeforeRenderFunc(sortByProgressFunc()), + ) total := 100 numBars := 3 - var wg sync.WaitGroup wg.Add(numBars) for i := 0; i < numBars; i++ { @@ -67,7 +69,6 @@ }() } - wg.Wait() p.Stop() fmt.Println("stop") } diff --git a/examples/stress/main.go b/examples/stress/main.go index df9f896..f4a2d8e 100644 --- a/examples/stress/main.go +++ b/examples/stress/main.go @@ -18,7 +18,7 @@ func main() { var wg sync.WaitGroup - p := mpb.New() + p := mpb.New(mpb.WithWaitGroup(&wg)) wg.Add(totalBars) for i := 0; i < totalBars; i++ { @@ -45,7 +45,6 @@ }() } - wg.Wait() p.Stop() fmt.Println("stop") } diff --git a/options.go b/options.go index de04a90..53bd9eb 100644 --- a/options.go +++ b/options.go @@ -3,6 +3,7 @@ import ( "io" "io/ioutil" + "sync" "time" "unicode/utf8" @@ -12,6 +13,16 @@ // ProgressOption is a function option which changes the default behavior of // progress pool, if passed to mpb.New(...ProgressOption) type ProgressOption func(*pConf) + +// WithWaitGroup provides means to have a single joint point. +// If *sync.WaitGroup is provided, you can safely call just p.Stop() +// without calling Wait() on provided *sync.WaitGroup. +// Makes sense when there are more than one bar to render. +func WithWaitGroup(wg *sync.WaitGroup) ProgressOption { + return func(c *pConf) { + c.ewg = wg + } +} // WithWidth overrides default width 80 func WithWidth(w int) ProgressOption { diff --git a/progress.go b/progress.go index 67fb1c7..32a95d7 100644 --- a/progress.go +++ b/progress.go @@ -25,6 +25,7 @@ width int format string rr time.Duration + ewg *sync.WaitGroup cw *cwriter.Writer ticker *time.Ticker beforeRender BeforeRender @@ -46,8 +47,10 @@ // Progress represents the container that renders Progress bars type Progress struct { - // WaitGroup for internal rendering sync + // wg for internal rendering sync wg *sync.WaitGroup + // External wg + ewg *sync.WaitGroup // quit channel to request p.server to quit quit chan struct{} @@ -74,6 +77,7 @@ } p := &Progress{ + ewg: conf.ewg, wg: new(sync.WaitGroup), done: make(chan struct{}), ops: make(chan func(*pConf)), @@ -138,11 +142,14 @@ } } -// Stop shutdowns Progress' goroutine. -// Should be called only after each bar's work done, i.e. bar has reached its -// 100 %. It is NOT for cancelation. Use WithContext or WithCancel for -// cancelation purposes. +// Stop is a way to gracefully shutdown mpb's rendering goroutine. +// It is NOT for cancelation (use mpb.WithContext for cancelation purposes). +// If *sync.WaitGroup has been provided via mpb.WithWaitGroup(), its Wait() +// method will be called first. func (p *Progress) Stop() { + if p.ewg != nil { + p.ewg.Wait() + } select { case <-p.quit: return