Section 7 of 11
Goroutines & channels
go f() costs ~2KB. A million of them is normal.
A goroutine is not an OS thread. The runtime multiplexes them M:N onto a handful of real threads, and their stacks start tiny and grow. This is exactly what Java 21 virtual threads copied — Go has had it since day one, which is why the whole stdlib is written blocking-style.
func() { process(job) } // a function literal — a VALUE, not called
func() { process(job) }() // the trailing () CALLS it
go func() { process(job) }() // `go` runs that call in a new goroutineGoroutines + WaitGroup
go.dev playground
Try it: Delete wg.Wait() and run again — main returns and kills every goroutine before they print.
Channels, select, and context cancellation
go.dev playground
Try it: Raise the worker sleep above the 200ms timeout and watch ctx.Done() win the select.
- chan T is a typed BlockingQueue. make(chan T) is unbuffered (a handoff); make(chan T, n) is buffered.
- <-chan T is receive-only, chan<- T is send-only — the arrow in the type is a compile-time direction check.
- select waits on several channels at once. With a default case it becomes non-blocking.
- context.Context carries deadlines and cancellation down the call tree. By convention it is the first parameter.
- go test -race catches real data races. Run it in CI.