skip to content
Sandip's Blog
Table of Contents

1. Goroutines

  • go f(): cheap, multiplexed onto OS threads
  • Goroutine lifecycle: every goroutine needs a way to stop
  • Goroutine leaks and how to find them
  • The scheduler (G-M-P model): conceptual understanding
  • GOMAXPROCS

Visual: the G-M-P scheduler

flowchart TB
  subgraph P1["P1 (logical processor)"]
    Q1["Local run queue: G1, G2, G3"]
  end
  subgraph P2["P2 (logical processor)"]
    Q2["Local run queue: G4"]
  end
  GQ["Global run queue"]
  P1 --> M1["M1 (OS thread)"]
  P2 --> M2["M2 (OS thread)"]
  GQ -.-> P1
  GQ -.-> P2
  Q1 -. "work stealing" .-> Q2

G = goroutine, M = OS thread, P = processor token. Idle Ps steal work from busy ones.

2. Channels

  • Unbuffered vs buffered
  • Send, receive, close, receiving from closed channel, v, ok := <-ch
  • Only the sender closes; closing twice panics
  • Nil channels block forever (useful in select)
  • Directional channels: chan<-, <-chan
  • for v := range ch

3. select

  • Multiplexing, default case, timeouts with time.After
  • Combining with context.Done()
  • Non-blocking send/receive

4. sync package

  • sync.WaitGroup (and wg.Go in newer Go versions)
  • sync.Mutex, sync.RWMutex
  • sync.Once, sync.OnceFunc, sync.OnceValue
  • sync.Pool
  • sync.Map (and when not to use it)
  • sync.Cond (rare)
  • sync/atomic and typed atomics (atomic.Int64)
  • golang.org/x/sync/errgroup and semaphore

5. context package

  • context.Background, WithCancel, WithTimeout, WithDeadline, WithValue
  • Context is the first parameter: func F(ctx context.Context, ...)
  • Never store context in structs
  • Propagate cancellation through every layer
  • WithValue for request-scoped data only, never optional params

Visual: context cancellation tree

flowchart TD
  R["context.Background()"] --> A["WithCancel: request ctx"]
  A --> B["WithTimeout: DB query"]
  A --> C["WithTimeout: external API call"]
  A --> D["Worker goroutines"]
  A -. "cancel()" .-> X["All children receive ctx.Done()"]

6. Patterns

  • Worker pool
  • Fan-out / fan-in
  • Pipeline
  • Producer-consumer
  • Rate limiting (time.Ticker, x/time/rate)
  • Pub/sub
  • Graceful shutdown
  • Bounded parallelism

Visual: worker pool and fan-in

flowchart LR
  P["Producer"] --> J[("jobs channel")]
  J --> W1["Worker 1"]
  J --> W2["Worker 2"]
  J --> W3["Worker 3"]
  W1 --> R[("results channel")]
  W2 --> R
  W3 --> R
  R --> C["Collector"]

Close jobs when the producer is done, wait on a WaitGroup, then close results.

7. Bugs you must be able to spot

  • Data races (use go test -race always)
  • Deadlocks (all goroutines are asleep)
  • Goroutine leaks (blocked forever on a channel)
  • Copying a mutex
  • Closure capturing a loop variable (pre-1.22)
  • Forgetting wg.Add before launching the goroutine

8. Philosophy

“Don’t communicate by sharing memory; share memory by communicating.”

But in practice: use a mutex for protecting simple state, channels for passing ownership and coordinating work.

9. The Go memory model

Practice

  1. Build a concurrent web crawler with a depth limit and a worker pool
  2. Implement a rate limiter using channels
  3. Build a pipeline: read file -> parse -> transform -> write, with cancellation
  4. Write a graceful-shutdown HTTP server
  5. Intentionally create a race, then catch it with -race

Previous: Go Mastery 2: Interfaces, Errors, Generics, Modules
Next: Go Mastery 4: Standard Library and Tooling

Back to the roadmap