How it works
The explanation, with examples you can run.
Blocking rules
- Unbuffered (
make(chan T)): a send waits until a receiver takes the value, and a receive waits for a sender. The hand-off is a meeting point. - Buffered (
make(chan T, n)): sends succeed while the buffer has room, and receives succeed while it holds values. When it is full, the sender waits: a buffered channel is a bounded queue, with backpressure built in. - nil channel: sends and receives block forever.
- closed channel: receives never block (they return the zero value once drained); sends and a second
closepanic.
package main import ( "fmt" "time") func produce(n int) <-chan int { out := make(chan int) // unbuffered: each send waits for a receiver go func() { defer close(out) // the sender closes, so range below can end for i := range n { out <- i * i } }() return out} func main() { for v := range produce(4) { fmt.Print(v, " ") } fmt.Println() jobs := make(chan string, 2) // buffered: two sends succeed with no receiver jobs <- "a" jobs <- "b" fmt.Println(len(jobs), cap(jobs)) slow := make(chan string) go func() { time.Sleep(50 * time.Millisecond) slow <- "done" }() select { case msg := <-slow: fmt.Println(msg) case <-time.After(10 * time.Millisecond): fmt.Println("timed out") } close(jobs) v, ok := <-jobs fmt.Println(v, ok) // buffered values are still delivered after close <-jobs v, ok = <-jobs fmt.Printf("%q %v\n", v, ok) // drained and closed: zero value, false}This prints 0 1 4 9, 2 2, timed out, a true and "" false. The return type <-chan int is receive-only, so callers can't send on it or close it. Note one small leak: after the timeout, the goroutine sending on slow blocks forever, because nobody receives. Making slow buffered with room for one value lets it finish.
Who closes
Close only to tell receivers "no more values", and only from the sending side: a for v := range ch loop ends when the channel is closed and drained. You don't have to close a channel to free it; an unreachable channel is collected like anything else. With several senders, close after all of them are done, typically from a goroutine that waits on a sync.WaitGroup.
select
select waits until one of its cases can proceed. If several can, it picks one uniformly at random, so no case can starve the others. A default case makes it non-blocking. Common uses:
- Timeouts: a case on
time.After(d)or, better, onctx.Done(). - Cancellation: every loop that sends should also select on a "stop" channel or a context, or it may block forever once the receiver gives up.
- Turning off a case: set a channel variable to
niland its case never fires.
Channel or mutex?
Channels suit handing over ownership, pipelines, fan-out to workers and signalling. A mutex is simpler for protecting a small piece of shared state such as a counter or a cache map. Use whichever makes the code shorter and clearer.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.