WaitGroup
The conc.WaitGroup
is a fundamental building block for structured concurrency in conc
. It is a direct, safer replacement for sync.WaitGroup
.
Its primary purpose is to run a group of goroutines and wait for them all to complete. Unlike the standard library's WaitGroup
, conc.WaitGroup
provides built-in panic handling.
Key Features
- Automatic Panic Recovery: If a goroutine started with
wg.Go()
panics, the panic is caught. - Panic Propagation: The caught panic is re-thrown by
wg.Wait()
, ensuring that panics are not silently ignored. - Simple API: The API is minimal and easy to use, closely resembling
sync.WaitGroup
but without manualAdd()
andDone()
calls.
Usage
Creating a WaitGroup
You can create a WaitGroup
using its zero value or with the NewWaitGroup()
constructor.
// Zero value is ready to use
var wg conc.WaitGroup
// Or using the constructor
wg2 := conc.NewWaitGroup()
Spawning Goroutines with Go()
The Go(f func())
method starts a new goroutine to execute the function f
. It automatically handles the internal counter, similar to calling Add(1)
and Done()
in sync.WaitGroup
.
var wg conc.WaitGroup
wg.Go(func() {
fmt.Println("Hello from a goroutine!")
})
Waiting for Completion with Wait()
The Wait()
method blocks until all goroutines started with Go()
have finished. If any of the goroutines panicked, Wait()
will propagate the first panic it captured.
package main
import (
"fmt"
"sync/atomic"
"github.com/sourcegraph/conc"
)
func main() {
var count atomic.Int64
var wg conc.WaitGroup
for i := 0; i < 10; i++ {
wg.Go(func() {
count.Add(1)
})
}
// This call will block until all 10 goroutines are done.
wg.Wait()
fmt.Println(count.Load()) // Output: 10
}
Panic Propagation Example
This example shows how a panic in a child goroutine is caught and re-panicked by Wait()
.
func TestWaitGroupPanic(t *testing.T) {
var wg conc.WaitGroup
wg.Go(func() {
panic("super bad thing")
})
// The call to wg.Wait() will panic.
require.Panics(t, wg.Wait)
}
Recovering Panics with WaitAndRecover()
If you need to handle the panic as a value instead of re-panicking, you can use WaitAndRecover()
. It returns a *panics.Recovered
object if a panic occurred, or nil
otherwise.
func ExampleWaitGroup_WaitAndRecover() {
var wg conc.WaitGroup
wg.Go(func() {
panic("super bad thing")
})
recoveredPanic := wg.WaitAndRecover()
if recoveredPanic != nil {
fmt.Println(recoveredPanic.Value) // Output: super bad thing
}
}