toolchains/golang/golang-concurrency-patterns/SKILL.md
Go concurrency patterns for production services: context cancellation, errgroup, worker pools, bounded parallelism, fan-in/fan-out, and common race/deadlock pitfalls
npx skillsauth add bobmatnyc/claude-mpm-skills golang-concurrency-patternsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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.
Default building blocks
context to drive cancellation and deadlines.errgroup.WithContext for fan-out/fan-in with early abort.Avoid
time.After inside hot loops.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
}
}()
✅ 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
}
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()
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()
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()
}
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
}
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()
}
}
errgroup.WithContextsync.WaitGroupRun targeted tests with the race detector and disable caching during debugging:
go test -race ./...
go test -run TestName -race -count=1 ./...
✅ 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)
}
}
Actions:
context.WithTimeout) around blocking operations.ok == false.<-limit release in semaphore patterns.go test -race reports)Actions:
Actions:
ctx.Done().time.Ticker is stopped and channels are closed by senders.context.Background() inside request paths; propagate caller context.errgroup: https://pkg.go.dev/golang.org/x/sync/errgrouptools
Xquik X data automation API - Use REST or MCP for tweet search, user lookup, follower exports, media downloads, monitors, webhooks, giveaway draws, and confirmation-gated X actions.
tools
LinkedIn automation via the Linked API CLI - fetch profiles, search people and companies, send messages, manage connections, create posts, react, comment, and run Sales Navigator and custom workflows. Use when the user wants to interact with LinkedIn.
tools
MCP (Model Context Protocol) server build and evaluation guide, including local conventions for tool surfaces, config, and testing
tools
MCP (Model Context Protocol) - Build AI-native servers with tools, resources, and prompts. TypeScript/Python SDKs for Claude Desktop integration.