Concurrent Task Execution with WaitGroup
Runs several independent tasks concurrently and waits for them while collecting any failures into a single report.
main.go
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576
package main
import (
"fmt"
"sync"
"time"
)
// Task represents a unit of work to be executed concurrently
type Task struct {
ID int
DelayMs int
}
// Execute runs the task with a simulated delay and returns an error if any
func (t *Task) Execute() error {
fmt.Printf("Starting task %d (delay: %dms)\n", t.ID, t.DelayMs)
time.Sleep(time.Duration(t.DelayMs) * time.Millisecond)
// Simulate occasional error for demonstration
if t.ID%3 == 0 {
return fmt.Errorf("task %d failed: simulated error", t.ID)
}
fmt.Printf("Completed task %d\n", t.ID)
return nil
}
func main() {
// Create a list of tasks
tasks := []Task{
{ID: 1, DelayMs: 100},
{ID: 2, DelayMs: 200},
{ID: 3, DelayMs: 150},
{ID: 4, DelayMs: 50},
}
// WaitGroup to wait for all goroutines to finish
var wg sync.WaitGroup
// Channel to collect errors from goroutines
errChan := make(chan error, len(tasks))
// Launch a goroutine for each task
for _, task := range tasks {
wg.Add(1)
// Use loop variable copy to avoid race condition
t := task
go func() {
defer wg.Done()
if err := t.Execute(); err != nil {
errChan <- err
}
}()
}
// Wait for all goroutines to complete then close error channel
go func() {
wg.Wait()
close(errChan)
}()
// Collect and print all errors
var errors []error
for err := range errChan {
errors = append(errors, err)
}
if len(errors) > 0 {
fmt.Printf("\nEncountered %d errors:\n", len(errors))
for _, err := range errors {
fmt.Printf("- %v\n", err)
}
} else {
fmt.Println("\nAll tasks completed successfully!")
}
}How It Works
Runs several independent tasks concurrently and waits for them while collecting any failures into a single report.
Builds a slice of Task structs, spins each one in its own goroutine with a WaitGroup, pushes errors into a buffered channel, then closes the channel after WaitGroup.Wait() to aggregate and print results.
Key Concepts
- 1Copies loop variables before launching goroutines to avoid races.
- 2Buffers the error channel to the task count so sends never block.
- 3Uses WaitGroup to coordinate goroutine completion before closing channels.
- 4Collates errors after the channel is closed and reports them once.
When to Use This Pattern
- Fan-out workloads like API calls or file processing.
- Speeding up independent jobs inside CLI tools or scripts.
- Teaching safe goroutine patterns with error collection.
- Prototype for bounded worker pools or pipeline stages.
Best Practices
- Prefer contexts when tasks should be cancelable.
- Right-size buffered channels to avoid goroutine leaks.
- Limit concurrency if the task list can grow without bound.
- Handle panics inside goroutines so the WaitGroup always completes.
Go Version1.13+
Difficultyintermediate
Production ReadyYes
Lines of Code76