Day 060 — select & time.After¶
Month 3 · Week 1 · ⬅ Day 059 · Day 061 ➡ · Journal index
🎯 Learning Objective¶
Wait on multiple channel operations at once with select, and bound any blocking operation with a timeout using time.After/time.NewTimer.
📚 Topics¶
selectsemantics: blocking, random choice among ready cases,defaulttime.Aftertimeouts;time.NewTimer/time.NewTickerand why youStopthem- Labelled
breakto leave aselectloop
📖 Reading / Sources¶
- Tour of Go — Select
- Tour of Go — Default Selection
- Go spec — Select statements
-
time.After/time.NewTimerdocs
📝 Notes¶
selectblocks until one of itscasechannel ops can proceed, then runs that one. If several are ready it chooses uniformly at random — there is no top-to-bottom priority → [[select]].- A
defaultcase makesselectnon-blocking: if no other case is ready right now,defaultruns. This is how you do non-blocking send/receive and polling. - An empty
select {}blocks forever (occasionally used to parkmain). time.After(d)returns a<-chan Timethat fires once afterd— the idiomatic one-shot timeout in aselect. It's a thin wrapper overtime.NewTimer(d).C→ [[time-after]].- Timer leak trap: a
time.Afterinside a loop allocates a fresh timer each pass; the old ones live until they fire. In hot loops usetime.NewTimerandStop()/Reset()it instead. (Go 1.23+ makes unreferencedtime.Aftertimers eligible for GC sooner, but the idiom still holds.) time.NewTicker(d)fires repeatedly everyd; alwaysdefer ticker.Stop()or it leaks → [[ticker]].- Plain
breakonly exits theselect, not an enclosingfor. Use a labelled break (break loop) to leave the loop → [[labelled-break]].
💻 Code Examples¶
select {
case r := <-slow:
fmt.Println("got:", r)
case <-time.After(20 * time.Millisecond):
fmt.Println("timed out") // fires if slow hasn't produced in time
}
Full code:
examples/month-03/select-timeout/main.go· Run:go run ./examples/month-03/select-timeout
🏋️ Exercises / Practice¶
| Exercise | Status | Link |
|---|---|---|
| Worker pool over a jobs channel (select-driven dispatch) | ✅ | exercises/month-03/week-1/workerpool/ |
🐛 Mistakes Made¶
- Used
breakinside aselectin aforloop expecting to exit the loop — it only left theselect; the loop spun forever. Added a label. - Put
time.Afterin a tight loop and watched the timer count climb under-race/pprof. Switched to a single reusabletime.NewTimer.
❓ Open Questions¶
- When should I reach for
context.WithTimeoutinstead oftime.After? (Answer preview: when the timeout must propagate/cancel downstream calls — that's Month 3 later.)
🧠 Active Recall (answer without looking)¶
-
Q: If two
selectcases are ready at the same time, which runs?
A
One is chosen uniformly at random — `select` has no case priority or ordering. -
Q: Why prefer
time.NewTimerovertime.Afterinside a loop?
A
`time.After` allocates a new timer every iteration that lives until it fires (a leak in hot loops). A `Timer` can be `Stop`/`Reset` and reused.
🪶 Feynman Reflection¶
select is a receptionist watching several phone lines: it waits until any line rings, picks up exactly one (flipping a coin if several ring together), and a default line means "if nobody's calling, do something else immediately." time.After is an egg-timer you add as one more line so you never wait indefinitely.
🕳️ Knowledge Gaps¶
- Fairness guarantees under heavy contention across many cases — revisit with the scheduler internals.
✅ Summary¶
I can multiplex channel operations with select, make them non-blocking with default, and bound blocking work with time.After/Timer, escaping the loop cleanly with a labelled break.
⏭️ Next Steps / Prep for Tomorrow¶
- Day 061: deadlocks — how they happen and how to spot them fast.
| Time spent | Difficulty | Confidence |
|---|---|---|
| 90 min | 🟦🟦⬜⬜⬜ | 🟦🟦🟦⬜⬜ |
Suggested commit: feat(examples): select and time.After timeouts (day 060)