Skip to content

Day 062 — Generators & done channels

Month 3 · Week 1 · ⬅ Day 061 · Day 063 ➡ · Journal index

🎯 Learning Objective

Build generators that return receive-only channels, compose them into pipelines, and use a done channel so producers shut down cleanly and never leak.

📚 Topics

  • The generator pattern: a function returning <-chan T backed by a goroutine
  • Pipeline stages (Gen → Map → Filter → Collect) and fan-in (merge)
  • The done-channel cancellation idiom (the precursor to context)

📖 Reading / Sources

📝 Notes

  • A generator is a function that creates a channel, launches a goroutine to fill it, and returns the channel receive-only (<-chan T). The caller just ranges over it — the goroutine is an implementation detail → [[generator]].
  • A pipeline chains stages, each taking an input channel and returning an output channel it owns and closes. Because a single channel is FIFO, a linear pipeline preserves order → [[pipeline]].
  • Fan-in / merge combines N channels into one: one forwarding goroutine per input, plus a sync.WaitGroup closer goroutine that closes the output exactly once after all inputs drain. Order across inputs is not guaranteed → [[fan-in]].
  • The leak problem: if the consumer stops reading early, a generator blocked on out <- v stays parked forever → [[goroutine-leak]]. Fix: pass a done <-chan struct{} and select between out <- v and <-done; closing done unblocks every parked send → [[done-channel]].
  • Closing done is a broadcast: every goroutine selecting on it observes the close at once — the clean way to cancel a whole fan-out. defer close(done) in the consumer guarantees cleanup on every exit path.
  • This done-channel idiom is exactly what context.Context generalises (cancellation + deadlines + values), which is the next big topic this month.

💻 Code Examples

func gen(done <-chan struct{}, nums ...int) <-chan int {
    out := make(chan int)
    go func() {
        defer close(out)
        for _, n := range nums {
            select {
            case out <- n:
            case <-done: // consumer quit — abandon and clean up, no leak
                return
            }
        }
    }()
    return out
}

Full code: examples/month-03/generator/main.go · Run: go run ./examples/month-03/generator

🏋️ Exercises / Practice

Exercise Status Link
Channel pipeline: Gen → Map → Filter → Collect exercises/month-03/week-1/pipeline/
Fan-in Merge that closes the output exactly once exercises/month-03/week-1/fanin/

🐛 Mistakes Made

  • Wrote a generator with no cancellation; an early break in the consumer leaked its goroutine. Added a done channel and a select.
  • Closed the merge output from inside each forwarder → double-close panic. Moved the close to a single closer goroutine after wg.Wait().

❓ Open Questions

  • When does context fully replace the raw done channel? (Almost always in real code — done is the teaching version; context adds deadlines, values, and a standard API.)

🧠 Active Recall (answer without looking)

  1. Q: Why return <-chan T (receive-only) from a generator instead of chan T?

    A It enforces that callers only receive — they can't accidentally send to or close a channel the generator owns. The compiler guards the contract.

  2. Q: How does a done channel stop a generator that's blocked on a send?

    A The generator `select`s between `out <- v` and `<-done`. Closing `done` makes its receive ready immediately, so the parked send is abandoned and the goroutine returns instead of leaking.

🪶 Feynman Reflection

A generator is a vending machine that quietly restocks itself: you keep pressing "next" and items appear, never seeing the worker in back. A done channel is the master "closing time" switch — flip it (close it) and every worker, no matter what they're mid-doing, hears it at once and goes home, so nobody is left waiting at a locked counter.

🕳️ Knowledge Gaps

  • Bounded-parallelism pipelines (fan-out then fan-in with N workers per stage) — practise with the worker pool plus merge.

✅ Summary

I can write order-preserving pipeline stages, fan several channels into one with a close-once merge, and prevent goroutine leaks with a broadcast done channel — the mental model context builds on.

⏭️ Next Steps / Prep for Tomorrow

  • Day 063: week review + active recall across goroutines, channels, select, and patterns.

Time spent Difficulty Confidence
90 min 🟦🟦🟦⬜⬜ 🟦🟦🟦⬜⬜

Suggested commit: feat(examples): generators, done channels and fan-in (day 062)