Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
bobmatnyc avatar

Golang Concurrency Patterns

  • 297 installs
  • 63 repo stars
  • Updated July 18, 2026
  • bobmatnyc/claude-mpm-skills

golang-concurrency-patterns is a Go development skill that implements safe concurrency with goroutines, channels, sync primitives, worker pools, and context-driven cancellation for developers building parallel backend se

About

golang-concurrency-patterns is a claude-mpm-skills guide for implementing safe Go concurrency in backend services and CLIs. The skill covers goroutines, channels, sync primitives, worker pools, and context-driven cancellation patterns that prevent races, leaks, and unbounded parallelism. Developers reach for golang-concurrency-patterns when adding parallel request handling, background workers, pipeline stages, or graceful shutdown to Go APIs and tools. It targets production-grade concurrency design rather than introductory Go syntax.

  • goroutine lifecycle
  • channel communication patterns
  • sync.WaitGroup and mutexes
  • context cancellation
  • worker pool designs

Golang Concurrency Patterns by the numbers

  • 297 all-time installs (skills.sh)
  • Ranked #31 of 98 Go skills by installs in the Skillselion catalog
  • Data as of Aug 1, 2026 (Skillselion catalog sync)
npx skills add https://github.com/bobmatnyc/claude-mpm-skills --skill golang-concurrency-patterns

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs297
repo stars63
Last updatedJuly 18, 2026
Repositorybobmatnyc/claude-mpm-skills

How do you implement safe goroutines and worker pools in Go?

Implement safe Go concurrency with goroutines, channels, sync primitives, worker pools, and context-driven cancellation.

Who is it for?

Go backend developers adding parallel processing, worker pools, or cancellation-aware concurrency to APIs and CLI tools.

Skip if: Developers writing single-threaded Go scripts or frontends that do not need goroutine-level concurrency design.

When should I use this skill?

User asks to implement goroutines, channels, worker pools, sync primitives, or context cancellation in Go backend code.

What you get

Go code using goroutines, channels, sync primitives, worker pools, and context-driven cancellation without race or leak patterns.

  • concurrent Go service code
  • worker pool implementations

Files

SKILL.mdMarkdownGitHub ↗

Go Concurrency Patterns (Production)

Overview

Go concurrency scales when goroutine lifetimes are explicit, cancellation is propagated with context.Context, and shared state is protected (channels or locks). Apply these patterns to build reliable services and avoid common failure modes: goroutine leaks, deadlocks, and data races.

Quick Start

Default building blocks

  • Use context to drive cancellation and deadlines.
  • Use errgroup.WithContext for fan-out/fan-in with early abort.
  • Bound concurrency (avoid unbounded goroutines) with a semaphore or worker pool.
  • Prefer immutable data; otherwise protect shared state with a mutex or make a single goroutine the owner.

Avoid

  • Fire-and-forget goroutines in request handlers.
  • time.After inside hot loops.
  • Closing channels from the receiver side.
  • Sharing mutable variables across goroutines without synchronization.

Core Concepts

Goroutine lifecycle

Treat goroutines as resources with a clear owner and shutdown condition.

Correct: stop goroutines via context

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

go func() {
    ticker := time.NewTicker(250 * time.Millisecond)
    defer ticker.Stop()

    for {
        select {
        case <-ctx.Done():
            return
        case <-ticker.C:
            // do work
        }
    }
}()

Wrong: goroutine without a stop condition

go func() {
    for {
        doWork() // leaks forever
    }
}()

Channels vs mutexes (choose intentionally)

  • Use channels to model ownership/serialization of state or to pipeline work.
  • Use mutexes to protect shared in-memory state with simple read/write patterns.

Correct: one goroutine owns the map

type req struct {
    key   string
    reply chan<- int
}

func mapOwner(ctx context.Context, in <-chan req) {
    m := map[string]int{}
    for {
        select {
        case <-ctx.Done():
            return
        case r := <-in:
            r.reply <- m[r.key]
        }
    }
}

Correct: mutex protects shared map

type SafeMap struct {
    mu sync.RWMutex
    m  map[string]int
}

func (s *SafeMap) Get(k string) (int, bool) {
    s.mu.RLock()
    defer s.mu.RUnlock()
    v, ok := s.m[k]
    return v, ok
}

Patterns

1) Fan-out/fan-in with cancellation (errgroup)

Use errgroup.WithContext to run concurrent tasks, cancel siblings on error, and wait for completion.

Correct: cancel on first error

g, ctx := errgroup.WithContext(ctx)

for _, id := range ids {
    id := id // capture
    g.Go(func() error {
        return process(ctx, id)
    })
}

if err := g.Wait(); err != nil {
    return err
}

Wrong: WaitGroup loses the first error and does not propagate cancellation

var wg sync.WaitGroup
for _, id := range ids {
    wg.Add(1)
    go func() {
        defer wg.Done()
        _ = process(context.Background(), id) // ignores caller ctx + captures id
    }()
}
wg.Wait()

2) Bounded concurrency (semaphore pattern)

Bound parallelism to prevent CPU/memory exhaustion and downstream overload.

Correct: bounded fan-out

limit := make(chan struct{}, 8) // max 8 concurrent
g, ctx := errgroup.WithContext(ctx)

for _, id := range ids {
    id := id
    g.Go(func() error {
        select {
        case <-ctx.Done():
            return ctx.Err()
        case limit <- struct{}{}:
        }
        defer func() { <-limit }()

        return process(ctx, id)
    })
}

return g.Wait()

3) Worker pool (durable throughput)

Use a fixed number of workers for stable throughput and predictable resource usage.

Correct: worker pool with context stop

type Job struct{ ID string }

func runPool(ctx context.Context, jobs <-chan Job, workers int) error {
    g, ctx := errgroup.WithContext(ctx)

    for i := 0; i < workers; i++ {
        g.Go(func() error {
            for {
                select {
                case <-ctx.Done():
                    return ctx.Err()
                case j, ok := <-jobs:
                    if !ok {
                        return nil
                    }
                    if err := handleJob(ctx, j); err != nil {
                        return err
                    }
                }
            }
        })
    }

    return g.Wait()
}

4) Pipeline stages (fan-out between stages)

Prefer one-directional channels and close only from the sending side.

Correct: sender closes

func stageA(ctx context.Context, out chan<- int) {
    defer close(out)
    for i := 0; i < 10; i++ {
        select {
        case <-ctx.Done():
            return
        case out <- i:
        }
    }
}

Wrong: receiver closes

func stageB(in <-chan int) {
    close(in) // compile error in<-chan; also wrong ownership model
}

5) Periodic work without leaks (time.Ticker vs time.After)

Use time.NewTicker for loops; avoid time.After allocations in hot paths.

Correct: ticker

t := time.NewTicker(1 * time.Second)
defer t.Stop()

for {
    select {
    case <-ctx.Done():
        return
    case <-t.C:
        poll()
    }
}

Wrong: time.After in loop

for {
    select {
    case <-ctx.Done():
        return
    case <-time.After(1 * time.Second):
        poll()
    }
}

Decision Trees

Channel vs Mutex

  • Need ownership/serialization (single writer, message passing) → use channel + owner goroutine
  • Need shared cache/map with many readers and simple updates → use RWMutex
  • Need simple counter with low contention → use atomic

WaitGroup vs errgroup

  • Need error propagation + sibling cancellation → use `errgroup.WithContext`
  • Need only wait and errors are handled elsewhere → use `sync.WaitGroup`

Buffered vs unbuffered channel

  • Need backpressure and synchronous handoff → use unbuffered
  • Need burst absorption up to a known size → use buffered (size with intent)
  • Unsure → start unbuffered and measure; add buffer only to remove known bottleneck

Testing & Verification

Race detector and flake control

Run targeted tests with the race detector and disable caching during debugging:

go test -race ./...
go test -run TestName -race -count=1 ./...

Timeouts to prevent hanging tests

Correct: test-level timeout via context

func TestSomething(t *testing.T) {
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()

    if err := doThing(ctx); err != nil {
        t.Fatal(err)
    }
}

Troubleshooting

Symptom: deadlock (test hangs, goroutines blocked)

Actions:

  • Add timeouts (context.WithTimeout) around blocking operations.
  • Verify channel ownership: only the sender closes; receivers stop on ok == false.
  • Check for missing <-limit release in semaphore patterns.

Symptom: data race (go test -race reports)

Actions:

  • Identify shared variables mutated by multiple goroutines.
  • Add a mutex or convert to ownership model (single goroutine owns state).
  • Avoid writing to captured loop variables.

Symptom: goroutine leak (memory growth, slow shutdown)

Actions:

  • Ensure every goroutine selects on ctx.Done().
  • Ensure time.Ticker is stopped and channels are closed by senders.
  • Avoid context.Background() inside request paths; propagate caller context.

Resources

  • Go Blog: Concurrency patterns: https://go.dev/blog/pipelines
  • errgroup: https://pkg.go.dev/golang.org/x/sync/errgroup
  • Go memory model: https://go.dev/ref/mem

Related skills

FAQ

What Go concurrency primitives does golang-concurrency-patterns cover?

golang-concurrency-patterns covers goroutines, channels, sync primitives, worker pools, and context-driven cancellation for safe parallel processing in Go backend services and CLI tools.

When should developers use golang-concurrency-patterns?

golang-concurrency-patterns fits when Go services need bounded parallelism, background workers, pipeline stages, or graceful shutdown without race conditions or goroutine leaks.

Gobackend

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.