Channels and select
Passing values between goroutines safely, and waiting on several at once.
- Write a producer that sends on a channel and closes it, and a consumer that ranges over it.
- Explain why an unbuffered send blocks until someone receives.
- Write a
selectloop that reads from two channels until both are closed.
What problem does a channel solve?
A channel lets goroutines hand each other values safely, and tells the receiver exactly when a value is ready. Goroutines run at the same time, so they need a safe way to hand each other results. Writing to a shared variable is a data race unless you add a lock, and even then the reader does not know *when* the value is ready.
A channel fixes both. One goroutine sends a value, another receives it, and the receive waits until the value is there. The handoff is the synchronisation: everything the sender did before the send is visible to the receiver after the receive.
Channels: a typed pipe between goroutines
jobs := make(chan int) // unbuffered: a send waits for a receiverqueue := make(chan int, 5) // buffered: holds 5 before a send blocksqueue <- 42 // send (fine here: the buffer has room)v := <-queue // receiveclose(queue) // "no more values are coming"for v := range queue { } // receives until the channel is closed and emptyv, ok := <-queue // ok is false once closed and drainedWhat is the difference between unbuffered and buffered channels?
An unbuffered channel makes the sender wait for a receiver; a buffered channel lets the sender continue until the buffer is full. An unbuffered channel is a rendezvous. The sender blocks until a receiver takes the value, so both goroutines know the handoff happened. That synchronisation is often the point, not a cost.
A buffered channel is a queue. The sender carries on until the buffer is full. Use one when you have a specific reason: a known number of results, or a signal that must never block the sender. A buffer size picked by feel hides backpressure: when the consumer falls behind, the buffer absorbs it, and you only find out later, under load.
Channel behaviour, all of it
| Operation | nil channel | open, unbuffered | open, buffered | closed |
|---|---|---|---|---|
| send | blocks forever | blocks until a receiver takes it | proceeds if the buffer has room, else blocks | panics |
| receive | blocks forever | blocks until a sender arrives | proceeds if the buffer has a value, else blocks | buffered values first, then zero value, ok=false |
| close | panics | succeeds; waiting receivers wake up, waiting senders panic | succeeds; buffered values can still be received | panics |
select — wait on several channels at once
select {case v := <-ch1: fmt.Println("from ch1:", v)case ch2 <- 1: fmt.Println("sent to ch2")case <-time.After(2 * time.Second): fmt.Println("timeout")default: fmt.Println("nothing ready right now") // makes select non-blocking}- Unbuffered channels synchronise; buffered ones queue. Default to unbuffered.
- The sender closes, never the receiver, and only one goroutine may close.
- Sending on a closed channel panics; receiving drains what is left, then gives the zero value and
ok=false. - A nil channel blocks forever, which is how you disable a
selectcase.
SumBoth(a, b): receive every value from both channels until both are closed, and return the total. The hidden tests use unbuffered channels fed by one goroutine that alternates between them, so ranging over a and then b deadlocks. Use select, and set a channel to nil once it is closed so select stops picking it. Example: b sends 1, a sends 2, a closes, b sends 3, b closes: the result is 6.Tab indents. Press Esc, then Tab, to leave the editor.
- 1Send on an unbuffered channel with no receiver and read the deadlock message.
- 2Close a channel then receive twice, checking
okeach time. - 3Write a
selectwith atime.Aftertimeout and make it fire. - 4Set a channel variable to
nilinside a select loop and watch that case switch off. - 5Predict the output: in
main,ch := make(chan int), thench <- 1, thenfmt.Println(<-ch). Then change it tomake(chan int, 1)and predict again. - 6Predict the output:
var a chan int(nil),b := make(chan int, 1),b <- 7, thenselect { case v := <-a: fmt.Println("a", v); case v := <-b: fmt.Println("b", v) }. Run it ten times. - 7Predict the output:
ch := make(chan int, 2), send 1 and 2,close(ch), then dov, ok := <-chthree times and print each.