Skip to content
gohacks.dev
Concurrency

Go Concurrency Cheat Sheet for Channels Locks and Context

A one page Go concurrency cheat sheet: channel behavior, select rules, sync primitives, atomics, context, common patterns and test commands.

4 min read
Summarize in AI
Flat illustration of one flow fanning out into parallel lanes of goroutines and joining back.

A reference to keep open while writing concurrent Go. It states rules and behavior; the linked articles carry the reasoning and the examples. Behavior shown here was checked on Go 1.25.6. For the bigger picture, start from the concurrency patterns hub.

Tested with Go 1.25.6

Channel operations by state

Operationnil channelopen, not full or emptyopen, full (send) or empty (receive)closed
ch <- vblocks foreversendsblockspanics
<-chblocks foreverreceivesblocksreturns buffered values, then zero value
v, ok := <-chblocks foreverok trueblocksok false once drained
close(ch)panicsclosesclosespanics
len(ch), cap(ch)0, 0count, capacitycount, capacityremaining count, capacity

Rules of thumb: the sender closes, never the receiver; one closer per channel; a channel does not need closing unless a receiver ranges over it (closing channels). Direction types document intent: chan<- T is send-only, <-chan T is receive-only (channels).

Go
for v := range ch { ... }        // receives until closed and drained
v, ok := <-ch                    // ok == false means closed and empty
select { case ch <- v: default: } // non-blocking send: drop if full

select

SituationResult
One case readythat case runs
Several cases readyone chosen uniformly at random
None ready, has defaultdefault runs
None ready, no defaultblocks until one is ready
case <-nilChannever chosen, so assigning nil disables a case
select {}blocks forever

Priority needs two selects: a non-blocking check of the urgent channel first, then the combined one (select fairness).

sync primitives

NeedUseNotes
Protect shared statesync.MutexLock, then defer Unlock; never copy
Many readers, rare writerssync.RWMutexcosts more than Mutex; no recursive read locks
Wait for goroutinessync.WaitGroupwg.Go(f) in Go 1.25; Add before go otherwise
Run oncesync.Once, OnceFunc, OnceValuelater callers see the first call's effects
Reuse temporary objectssync.Poolmay be emptied by the GC; not a cache
Condition waitingsync.Condprefer a channel; always Wait in a loop
Concurrent map, special casessync.Mapwrite-once read-many or disjoint keys
One counter, flag or pointersync/atomic typesatomic.Int64, atomic.Bool, atomic.Pointer[T]

Details: mutex, RWMutex, WaitGroup, Once, Pool, Cond, atomics.

context

FunctionPurpose
context.Background()root context for main, tests and top-level work
context.WithCancel(parent)manual cancellation; always defer cancel()
context.WithTimeout(parent, d)cancels after a duration
context.WithDeadline(parent, t)cancels at a time
context.WithCancelCause(parent)cancel with a reason; read it with context.Cause
context.WithoutCancel(parent)keeps values, drops cancellation (for cleanup)
context.AfterFunc(ctx, f)runs f when ctx ends
ctx.Err()context.Canceled or context.DeadlineExceeded once done

Conventions: first parameter, named ctx; never stored in a struct; values only for request-scoped data (context, cancellation).

Patterns, in one line each

PatternShapeArticle
Wait for N tasksWaitGroup, results in a slice by indexWaitGroup
First error cancels the resterrgroup.WithContexterrgroup
Bound concurrencysemaphore channel, errgroup.SetLimitbounded parallelism
Worker poolN workers range over a jobs channelworker pool
Fan-ingoroutine per input, closer after Waitmerging channels
Pipelinestage functions, channel to channelpipelines
Collapse duplicate callssingleflight.Group.Dosingleflight
Rate limitrate.Limiterrate limiting
Timeout a blocking callcontext.WithTimeout passed downtimeouts
Graceful shutdownsignal.NotifyContext, Server.Shutdownshutdown

Runtime errors and what they mean

MessageCause
fatal error: all goroutines are asleep - deadlock!every goroutine is blocked and none can proceed
fatal error: concurrent map writes (or read and map write)a map used from several goroutines without a lock; not recoverable
panic: send on closed channela sender used a channel that was already closed
panic: close of closed channel / close of nil channeldouble close, or closing a channel that was never made
panic: sync: negative WaitGroup countermore Done calls than Add
fatal error: sync: unlock of unlocked mutexUnlock without a matching Lock; not recoverable
context deadline exceeded / context canceledthe context ended; check with errors.Is

The memory model in six lines

A go statement happens before the goroutine starts. A send happens before the matching receive completes. A close happens before a receive that returns because of it. On an unbuffered channel, the receive happens before the send completes. Unlock happens before the next Lock. Once.Do(f) finishing happens before any Do returns. Without one of these edges, two accesses to the same variable race (happens-before).

Rules worth memorizing

  1. Every goroutine needs a way to stop and an owner that waits for it.
  2. Every blocking send, receive or call should also watch cancellation or have a deadline.
  3. Shared memory needs an ordering edge: a lock, an atomic, a channel operation or WaitGroup.Wait.
  4. Do not hold a lock across I/O, logging or callbacks.
  5. Do not start goroutines in a loop without a bound.
  6. A panic in any goroutine ends the process unless that goroutine recovers.

Commands

Shell
go test -race ./...                 # data races in the executed paths
go test -race -count=20 -shuffle=on # vary the interleavings and test order
go test -cpu 1,2,8                  # vary GOMAXPROCS
go vet ./...                        # copied locks, WaitGroup misuse, loop capture on old modules
GOTRACEBACK=all ./app               # print every goroutine on a crash
kill -QUIT <pid>                    # dump all goroutines and exit

Programs expose profiles with net/http/pprof: /debug/pprof/goroutine?debug=2 for a full dump, and /debug/pprof/mutex and /debug/pprof/block once the rates are set (mutex and block profiles). The reference documents are the Go memory model, the sync package and the context package.

Share

Keep reading

Get new articles by email

We send one email when a new article is published. No spam.

Unsubscribe in one click. No spam, ever.