agentsclimarketplace

Go concurrency

Skill muratmirgun/gophers/skills/go-concurrency

26 production-grade Go skills for Claude Code, Gemini CLI, and opencode.

Install
npx -y skills add muratmirgun/gophers --skill go-concurrency

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 8 stars8 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Use when writing or reviewing concurrent Go code — goroutines, channels, select, mutexes, atomics, errgroup, singleflight, worker pools, or fan-out/fan-in pipelines. Apply proactively whenever a goroutine is spawned, a shared field is mutated, or a channel is created, even if the user has not asked about concurrency. Does not cover context.Context patterns (see go-context).

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

7.9 KB, as published. Nobody here has run it

Go Concurrency

Goroutines are cheap, but every one you spawn is a resource you must own. The goal is structured concurrency: each goroutine has a clear owner, a predictable exit, and a way for the caller to wait and collect errors.

Core Rules

  1. Never start a goroutine without knowing how it will stop. A blocked goroutine is not garbage-collected — it leaks.
  2. The caller must be able to wait. Use sync.WaitGroup, errgroup.Group, or an explicit done channel.
  3. No goroutines in init(). Expose Start/Stop/Shutdown so callers control the lifecycle.
  4. Share by communicating. Default to channels; reach for sync.Mutex only when the problem is genuinely "protect a shared field".
  5. Only the sender closes a channel. Closing from the receiver side panics on the next send.
  6. Specify channel direction (chan<-, <-chan) at function boundaries — the compiler catches misuse.
  7. Always include ctx.Done() in select. Without it, the goroutine cannot be cancelled.
  8. Test for leaks with go.uber.org/goleak.

Primitive Decision

NeedUseWhy
Pass a value from producer to consumerChannelTransfers ownership explicitly
Wait for N fire-and-forget goroutinessync.WaitGroup (Go 1.25: wg.Go)No error needed
Wait + collect first error + cancel siblingserrgroup.WithContextStructured failure
Bound concurrency (worker pool)errgroup.SetLimit(n)Replaces hand-rolled pools
Protect a shared fieldsync.Mutex / sync.RWMutexShort critical section
Counter / flagtyped sync/atomic (atomic.Int64, atomic.Bool)Lock-free, type-safe
Read-heavy concurrent mapsync.MapConcurrent map read/write otherwise crashes
One-shot initsync.Once (or OnceFunc/OnceValue in 1.21+)Idempotent setup
Deduplicate concurrent callsx/sync/singleflightCache stampede prevention

Read references/sync-primitives.md when picking between mutex, atomic, sync.Map, sync.Pool, or singleflight, or when designing the field layout of a struct that protects shared state.

Goroutine Lifetimes

// Good: bounded WaitGroup, deterministic exit
var wg sync.WaitGroup
for item := range queue {
    wg.Add(1)
    go func(it Item) { defer wg.Done(); process(ctx, it) }(item)
}
wg.Wait()

// Bad: no stop signal, no wait — classic leak
go func() { for { flush(); time.Sleep(delay) } }()

Go 1.25+ exposes wg.Go(fn) which folds Add/Done into one call. Always call wg.Add before go — otherwise wg.Wait may return before the goroutine even starts.

errgroup: Errors and Cancellation

errgroup.WithContext is the right default when sibling goroutines should cancel each other on the first failure:

g, ctx := errgroup.WithContext(ctx)
g.SetLimit(8)
for _, url := range urls {
    g.Go(func() error { return fetch(ctx, url) })
}
if err := g.Wait(); err != nil {
    return fmt.Errorf("fetching urls: %w", err)
}

g.Wait returns the first non-nil error; ctx is cancelled as soon as any worker fails. See references/errgroup-and-pools.md.

Channels

func produce(out chan<- int)                  { /* send-only */ }
func consume(in <-chan int)                   { /* receive-only */ }
func transform(in <-chan int, out chan<- int) { /* ownership crosses */ }

Buffer size is 0 or 1. Anything larger must be justified (what bounds it under load, what happens when writers block).

In every long-running select, include <-ctx.Done(). Avoid time.After in hot loops — it allocates a timer per iteration; hoist a time.NewTimer and Reset it instead.

Read references/channels-and-select.md when implementing pipelines, fan-in/fan-out, broadcast via close, or non-blocking sends with default.

Mutexes and Atomics

The zero value of sync.Mutex/RWMutex is valid — almost never use a pointer. Do not embed mutexes; keep them as an unexported mu field so Lock/Unlock aren't public API. Keep critical sections short; never hold a lock across I/O. Prefer typed atomics (atomic.Bool, atomic.Int64) over raw sync/atomic on int32/int64 fields.

Testing: goleak and synctest

Wire go.uber.org/goleak into every package that spawns goroutines (goleak.VerifyTestMain(m) or defer goleak.VerifyNone(t)). For timer-dependent tests, use testing/synctest (Go 1.25+) so synthetic time advances deterministically. Go 1.26 adds an experimental goroutineleak pprof profile for production diagnosis — it is not a substitute for goleak in tests. See references/leaks-and-synctest.md.

Anti-Patterns

Anti-patternWhy it hurtsDo this instead
Fire-and-forget go func() with no signalLeaks on shutdown; can outlive its inputsPass ctx, use errgroup, or own a done channel
Closing a channel from the receiverPanics on the next sendOnly the sender closes
time.After in a hot loopAllocates a timer per iterationtime.NewTimer + Reset
select without ctx.Done()Cannot be cancelledAlways include the cancel case
wg.Add(1) inside the goroutineWait may return before Add runsAdd before go, or use wg.Go (Go 1.25+)
Buffered channel sized "to be safe"Hides backpressure, masks bugsSize 0 or 1; justify anything larger
Concurrent read+write on mapHard runtime crash, not a race warningsync.Map or sync.RWMutex + map
Mutex held across I/O / RPCSerializes the whole serviceCopy what you need under the lock; release before the call
Sending a pointer through a channelRe-introduces shared memorySend a copy or an immutable value
Forgetting -race in CIRaces ship to prodgo test -race ./... always

Verification Checklist

Before finishing a concurrency change:

  • Every go has a documented exit (ctx, done channel, or bounded loop)
  • Every long-running select has a <-ctx.Done() case
  • wg.Add is called before go, or wg.Go is used (Go 1.25+)
  • Channels are sized 0 or 1, or the size has a comment justifying it
  • Only the sender closes channels; receivers use for v := range ch or v, ok := <-ch
  • No mutex is held across network/disk I/O
  • go test -race ./... is clean
  • Packages that spawn goroutines wire goleak.VerifyTestMain or per-test VerifyNone

References

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.