DEV Community

Silver_dev
Silver_dev

Posted on

Concurrency patterns on Golang: ErrGroup

Problems this pattern can solve:

  1. You've launched 5 goroutines. You need to wait for all of them to complete, but if at least one fails with an error — stop the rest and return the error. Implementing this with sync.WaitGroup + context + error channels leads to boilerplate code that's easy to break.

  2. In the classic approach, if one goroutine fails with an error, the rest continue working uselessly or, worse, block forever writing to a channel that no one is reading from. This is a resource leak.

  3. The problem of complex error collection from multiple goroutines. You need to collect all errors or at least the first significant one. Manual implementation requires creating mutexes to protect a shared error variable or separate channels.

Essence: ErrGroup is a goroutine group manager that provides two main mechanisms:

  • Synchronization: Waiting for all launched goroutines to complete (like sync.WaitGroup).
  • Error propagation and cancellation: When an error occurs in any of the goroutines, the shared context is automatically canceled, signaling other goroutines to terminate, and the Wait() method returns this error.

Idea: Combine launching concurrent tasks, waiting for their completion, and automatically stopping all of them at the first error through a shared context.

Use the official package golang.org/x/sync/errgroup.

ErrGroup vs Other Patterns

Difference from sync.WaitGroup

  • sync.WaitGroup: A simple goroutine counter. Waits for all to complete, but doesn't handle errors and context. If a goroutine fails, the rest continue working.

  • ErrGroup: A wrapper over WaitGroup that adds context binding and "stop everything on first error" semantics.

Difference from Pipeline

  • Pipeline: A pattern for organizing sequential data processing stages, where data flows through channels.

  • ErrGroup: A pattern for managing concurrent tasks that may be independent or loosely coupled. Used for coordination, not for data stream transformation.

Difference from Worker Pool (Fan-out/Fan-in)

  • Worker Pool: About distributing tasks among a fixed number of workers for parallel processing.

  • ErrGroup: About coordinating the execution of a set of tasks (often heterogeneous) and reacting to the first error. ErrGroup can be used inside a worker or to manage a group of workers, but the focus is on cancellation, not task distribution.

Example

package main

import (
    "context"
    "fmt"
    "time"

    "golang.org/x/sync/errgroup"
)

func main() {
    // Create an errgroup with context
    g, ctx := errgroup.WithContext(context.Background())

    // Launch a goroutine that will complete successfully
    g.Go(func() error {
        select {
        case <-time.After(1 * time.Second):
            fmt.Println("Goroutine 1: success")
            return nil
        case <-ctx.Done():
            fmt.Println("Goroutine 1: canceled")
            return ctx.Err()
        }
    })

    // Launch a goroutine that will fail with an error
    g.Go(func() error {
        select {
        case <-time.After(500 * time.Millisecond):
            fmt.Println("Goroutine 2: error!")
            return fmt.Errorf("something went wrong in goroutine 2")
        case <-ctx.Done():
            fmt.Println("Goroutine 2: canceled")
            return ctx.Err()
        }
    })

    // Launch a goroutine that should be canceled
    g.Go(func() error {
        select {
        case <-time.After(3 * time.Second):
            fmt.Println("Goroutine 3: success (but this won't happen)")
            return nil
        case <-ctx.Done():
            fmt.Println("Goroutine 3: canceled")
            return ctx.Err() // context.Canceled
        }
    })

    // Wait for all goroutines to complete and get the first error
    if err := g.Wait(); err != nil {
        fmt.Printf("Error: %v\n", err)
    } else {
        fmt.Println("All successful")
    }

    // Give time to see the output
    time.Sleep(1 * time.Second)
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)