Loading...
Loading...
Go concurrency patterns including goroutine lifecycle management, channel usage, mutex handling, and sync primitives. Use when writing concurrent Go code, spawning goroutines, working with channels, or documenting thread-safety guarantees. Based on Google and Uber Go Style Guides.
npx skill4agent add cxuu/golang-skills go-concurrencyNormative: When you spawn goroutines, make it clear when or whether they exit.
// Bad: Sending on closed channel causes panic
ch := make(chan int)
ch <- 42
close(ch)
ch <- 13 // panic// Good: Goroutine lifetimes are clear
func (w *Worker) Run(ctx context.Context) error {
var wg sync.WaitGroup
for item := range w.q {
wg.Add(1)
go func() {
defer wg.Done()
process(ctx, item) // Returns when context is cancelled
}()
}
wg.Wait() // Prevent spawned goroutines from outliving this function
}// Bad: Careless about when goroutines finish
func (w *Worker) Run() {
for item := range w.q {
go process(item) // When does this finish? What if it never does?
}
}Source: Uber Go Style Guide
// Bad: No way to stop or wait for this goroutine
go func() {
for {
flush()
time.Sleep(delay)
}
}()// Good: Stop/done channel pattern for controlled shutdown
var (
stop = make(chan struct{}) // tells the goroutine to stop
done = make(chan struct{}) // tells us that the goroutine exited
)
go func() {
defer close(done)
ticker := time.NewTicker(delay)
defer ticker.Stop()
for {
select {
case <-ticker.C:
flush()
case <-stop:
return
}
}
}()
// To shut down:
close(stop) // signal the goroutine to stop
<-done // and wait for it to exitsync.WaitGroupvar wg sync.WaitGroup
for i := 0; i < N; i++ {
wg.Add(1)
go func() {
defer wg.Done()
// work...
}()
}
wg.Wait()done := make(chan struct{})
go func() {
defer close(done)
// work...
}()
<-done // wait for goroutine to finishinit()CloseStopShutdownSource: Uber Go Style Guide
// Bad: Spawns uncontrollable background goroutine
func init() {
go doWork()
}// Good: Explicit lifecycle management
type Worker struct {
stop chan struct{}
done chan struct{}
}
func NewWorker() *Worker {
w := &Worker{
stop: make(chan struct{}),
done: make(chan struct{}),
}
go w.doWork()
return w
}
func (w *Worker) Shutdown() {
close(w.stop)
<-w.done
}Principle: Never start a goroutine without knowing how it will stop.
sync.Mutexsync.RWMutexSource: Uber Go Style Guide
// Bad: Unnecessary pointer
mu := new(sync.Mutex)
mu.Lock()// Good: Zero-value is valid
var mu sync.Mutex
mu.Lock()// Bad: Embedded mutex exposes Lock/Unlock as part of API
type SMap struct {
sync.Mutex // Lock() and Unlock() become methods of SMap
data map[string]string
}
func (m *SMap) Get(k string) string {
m.Lock()
defer m.Unlock()
return m.data[k]
}// Good: Named field keeps mutex as implementation detail
type SMap struct {
mu sync.Mutex
data map[string]string
}
func (m *SMap) Get(k string) string {
m.mu.Lock()
defer m.mu.Unlock()
return m.data[k]
}LockUnlockNormative: Prefer synchronous functions over asynchronous functions.
// Good: Synchronous function - caller controls concurrency
func ProcessItems(items []Item) ([]Result, error) {
var results []Result
for _, item := range items {
result, err := processItem(item)
if err != nil {
return nil, err
}
results = append(results, result)
}
return results, nil
}
// Caller can add concurrency if needed:
go func() {
results, err := ProcessItems(items)
// handle results
}()Advisory: It is quite difficult (sometimes impossible) to remove unnecessary concurrency at the caller side. Let the caller add concurrency when needed.
Normative: Specify channel direction where possible.
// Good: Direction specified - clear ownership
func sum(values <-chan int) int {
total := 0
for v := range values {
total += v
}
return total
}// Bad: No direction - allows accidental misuse
func sum(values chan int) (out int) {
for v := range values {
out += v
}
close(values) // Bug! This compiles but shouldn't happen.
}Source: Uber Go Style Guide
// Bad: Arbitrary buffer size
c := make(chan int, 64) // "Ought to be enough for anybody!"// Good: Deliberate sizing
c := make(chan int, 1) // Size of one
c := make(chan int) // Unbuffered, size of zerofunc produce(out chan<- int) { /* send-only */ }
func consume(in <-chan int) { /* receive-only */ }
func transform(in <-chan int, out chan<- int) { /* both directions */ }sync/atomicint32int64Source: Uber Go Style Guide
// Bad: Easy to forget atomic operation
type foo struct {
running int32 // atomic
}
func (f *foo) start() {
if atomic.SwapInt32(&f.running, 1) == 1 {
return // already running
}
// start the Foo
}
func (f *foo) isRunning() bool {
return f.running == 1 // race! forgot atomic.LoadInt32
}// Good: Type-safe atomic operations
type foo struct {
running atomic.Bool
}
func (f *foo) start() {
if f.running.Swap(true) {
return // already running
}
// start the Foo
}
func (f *foo) isRunning() bool {
return f.running.Load() // can't accidentally read non-atomically
}go.uber.org/atomicatomic.Boolatomic.Int64Advisory: Document thread-safety when it's not obvious from the operation type.
Lookupselectdefaultsync.Pool| Topic | Guidance | Type |
|---|---|---|
| Goroutine lifetimes | Make exit conditions clear | Normative |
| Fire-and-forget | Don't do it - always have stop mechanism | Normative |
| Zero-value mutexes | Valid; don't use pointers | Advisory |
| Mutex embedding | Don't embed; use named field | Advisory |
| Synchronous functions | Prefer over async | Normative |
| Channel direction | Always specify | Normative |
| Channel size | One or none by default | Advisory |
| Atomic operations | Use go.uber.org/atomic | Advisory |
| Concurrency docs | Document when not obvious | Advisory |
| Anti-Pattern | Problem | Fix |
|---|---|---|
| Fire-and-forget goroutines | Resource leaks, undefined behavior | Use WaitGroup, done channel, or context |
| Goroutines in init() | Uncontrollable lifecycle | Use explicit object with Shutdown method |
| Embedded mutexes | Leaks Lock/Unlock into API | Use named |
| Pointer to mutex | Unnecessary indirection | Zero-value is valid |
| Arbitrary channel buffers | Hidden blocking issues | Default to 0 or 1 |
| Raw sync/atomic | Easy to forget atomic reads | Use go.uber.org/atomic |
| Undocumented thread-safety | Callers may race | Document when unclear |