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

Building blocks

Tool What it is
go f() Starts a goroutine: a function running concurrently, with a few KB of stack. Thousands are cheap.
chan T Typed pipe between goroutines. Unbuffered = handoff, buffered = queue.
select Wait on several channel operations at once; default makes it non-blocking.
context.Context Carries cancellation, deadlines and request-scoped values down the call tree.
sync.Mutex / RWMutex Plain locks, for when shared state is simpler than channels.
sync.WaitGroup Wait for a group of goroutines to finish.
sync.Once / OnceValue Lazy one-time initialisation.
errgroup (golang.org/x/sync) WaitGroup + first error + cancel the rest.
ch := make(chan int)
go func() { ch <- compute(1) }()
result := <-ch

Pattern: fan-out with a slow-consumer policy

One channel per subscriber, and a non-blocking send so a slow client can never stall the publisher.

type Conn struct {
    send   chan Event    // bounded buffer
    closed chan struct{}
}

// Broadcast never blocks: a subscriber that cannot keep up is dropped.
func (h *Hub) Broadcast(id SessionID, ev Event) {
    h.mu.RLock()
    conns := make([]*Conn, 0, len(h.conns[id]))
    for c := range h.conns[id] {
        conns = append(conns, c)
    }
    h.mu.RUnlock()

    for _, c := range conns {
        select {
        case c.send <- ev:
        default:
            h.drop(c) // buffer full: slow consumer
        }
    }
}

Each connection has its own writer goroutine serving several event sources:

for {
    select {
    case <-ctx.Done():
        return
    case <-c.closed:
        return
    case ev := <-c.send:
        write(ctx, ws, ev)
    case <-ping.C:
        ws.Ping(ctx)
    }
}

context.Context

wctx, cancel := context.WithTimeout(ctx, writeTimeout)
defer cancel()
return ws.Write(wctx, msg)

Pattern: periodic background work

// every runs f now and then every d until ctx ends.
func every(ctx context.Context, d time.Duration, f func()) {
    t := time.NewTicker(d)
    defer t.Stop()
    for {
        f()
        select {
        case <-ctx.Done():
            return
        case <-t.C:
        }
    }
}

go every(ctx, 10*time.Minute, sweepIdleSessions)

Pattern: graceful shutdown

srv := &http.Server{Addr: addr, Handler: h, ReadHeaderTimeout: 10 * time.Second}

errCh := make(chan error, 1)
go func() { errCh <- srv.ListenAndServe() }()

stop := make(chan os.Signal, 1)
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)

select {
case err := <-errCh:
    if errors.Is(err, http.ErrServerClosed) {
        return nil
    }
    return err
case <-stop:
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    return srv.Shutdown(ctx) // stops accepting, drains in-flight requests
}

signal.NotifyContext is a shorter alternative when all you need is a cancelled context.


Pitfalls

go test -race ./...