# Golang Concurrency

> When to activate: goroutines, channels, select, sync.WaitGroup, sync.Mutex, context cancellation, worker pools, rate limiting in Go

- Skill: `mattakushi432/golang-concurrency` (Agent Skill)
- Install (CLI): `npx skillmds@latest add mattakushi432/golang-concurrency`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mattakushi432/golang-concurrency/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: Mattakushi432 (https://skillmd.com/u/mattakushi432)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/mattakushi432/golang-concurrency

---


# Go Concurrency Patterns

## Goroutines and WaitGroup

```go
func processItems(items []Item) {
    var wg sync.WaitGroup
    for _, item := range items {
        wg.Add(1)
        go func(it Item) {
            defer wg.Done()
            process(it)
        }(item)  // pass item as argument to avoid closure capture bug
    }
    wg.Wait()
}
```

## Worker Pool

```go
func workerPool(ctx context.Context, jobs <-chan Job, results chan<- Result, workers int) {
    var wg sync.WaitGroup
    for i := 0; i < workers; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            for {
                select {
                case job, ok := <-jobs:
                    if !ok { return }
                    results <- process(job)
                case <-ctx.Done():
                    return
                }
            }
        }()
    }
    go func() {
        wg.Wait()
        close(results)
    }()
}

// Usage
jobs := make(chan Job, 100)
results := make(chan Result, 100)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

workerPool(ctx, jobs, results, 10)

for _, job := range allJobs { jobs <- job }
close(jobs)

for result := range results { handle(result) }
```

## Context for Cancellation

```go
func fetchWithTimeout(url string) ([]byte, error) {
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, fmt.Errorf("fetch %s: %w", url, err)
    }
    defer resp.Body.Close()
    return io.ReadAll(resp.Body)
}

// Propagate context through call stack
func (s *Service) Handle(ctx context.Context, req Request) (Response, error) {
    user, err := s.repo.FindUser(ctx, req.UserID)  // ctx passed down
    if err != nil { return Response{}, err }

    data, err := s.external.Fetch(ctx, user.ExternalID)
    return Response{Data: data}, err
}
```

## Channels Patterns

```go
// Pipeline
func generate(nums ...int) <-chan int {
    out := make(chan int)
    go func() {
        defer close(out)
        for _, n := range nums { out <- n }
    }()
    return out
}

func square(in <-chan int) <-chan int {
    out := make(chan int)
    go func() {
        defer close(out)
        for n := range in { out <- n * n }
    }()
    return out
}

// Fan-out + fan-in
func fanOut(in <-chan int, workers int) []<-chan int {
    channels := make([]<-chan int, workers)
    for i := range channels {
        channels[i] = square(in)
    }
    return channels
}

func merge(channels ...<-chan int) <-chan int {
    var wg sync.WaitGroup
    out := make(chan int)
    output := func(c <-chan int) {
        defer wg.Done()
        for n := range c { out <- n }
    }
    wg.Add(len(channels))
    for _, c := range channels { go output(c) }
    go func() { wg.Wait(); close(out) }()
    return out
}
```

## sync.Mutex and RWMutex

```go
type SafeCounter struct {
    mu sync.RWMutex
    v  map[string]int
}

func (c *SafeCounter) Inc(key string) {
    c.mu.Lock()
    defer c.mu.Unlock()
    c.v[key]++
}

func (c *SafeCounter) Value(key string) int {
    c.mu.RLock()
    defer c.mu.RUnlock()
    return c.v[key]
}
```

## sync.Once for Lazy Initialization

```go
type Config struct {
    once sync.Once
    data map[string]string
}

func (c *Config) Get(key string) string {
    c.once.Do(func() {
        c.data = loadConfigFromDisk()
    })
    return c.data[key]
}
```

## Rate Limiting

```go
// Token bucket using time.Ticker
func rateLimitedWorker(ctx context.Context, requests <-chan Request, rps int) {
    ticker := time.NewTicker(time.Second / time.Duration(rps))
    defer ticker.Stop()

    for req := range requests {
        select {
        case <-ticker.C:
            go handle(req)
        case <-ctx.Done():
            return
        }
    }
}

// golang.org/x/time/rate — production rate limiter
limiter := rate.NewLimiter(rate.Every(time.Second/10), 5)  // 10 rps, burst 5
if err := limiter.Wait(ctx); err != nil { return err }
```

## Common Anti-Patterns

- **Goroutine leak** — always ensure goroutines can exit; use context cancellation
- **Closure capturing loop variable** — pass loop var as argument to goroutine
- **Unbuffered channel + no receiver** — deadlock; size channels or use select with default
- **Mutex protecting wrong scope** — lock/unlock around the exact shared state, not the whole function
- **Using `time.Sleep` for synchronization** — use channels, WaitGroup, or condition variables

