Buffered and Directional Channels
Unbuffered channels force a rendezvous: every send waits for a receive. Two refinements make channels more flexible — buffers, which decouple the two sides, and directions, which document who does what.
Buffered channels
Pass a capacity to make and the channel gets an internal queue:
ch := make(chan int, 3)
ch <- 1 // doesn't block
ch <- 2 // doesn't block
ch <- 3 // doesn't block
ch <- 4 // blocks: buffer full, waits for a receive
Sends only block when the buffer is full; receives only block when it is
empty. Note that the deadlock example from last lesson now works — with
make(chan int, 1), main can send one value with no receiver around,
because the value parks in the buffer.
len(ch) reports how many values are queued right now and cap(ch) the
buffer size. They're occasionally handy for monitoring, but don't build
logic on len — it can change between the moment you read it and the
moment you act.
When buffering helps
A buffer is for smoothing bursts, not for correctness. Good uses:
- A producer that emits in bursts while the consumer drains steadily — a small buffer stops each side stalling the other.
- A known number of results:
make(chan result, n)letsnworkers each send once and exit immediately, whether or not anyone has received yet.
If your program only works because you made a buffer "big enough", it's broken — you've just made the deadlock rarer. Start unbuffered; add a measured buffer when you can say why.
Directional channels
A channel variable can be restricted to one direction:
chan int // send and receive
chan<- int // send-only (arrow points into the channel)
<-chan int // receive-only (arrow points out of it)
You rarely make a directional channel; you declare them in function signatures, and Go converts the bidirectional channel automatically:
func produce(out chan<- int) { ... } // this function only sends
func consume(in <-chan int) { ... } // this function only receives
This is API documentation the compiler enforces. consume cannot send on
in or close it — it won't compile. A return type of <-chan int tells
callers "you receive from this; the sender and closer live inside." The
generate function you wrote last lesson had exactly that shape.
Pipelines
Directions shine when you chain stages, each a function that receives from an upstream channel and returns a downstream one:
nums := generate(5) // <-chan int: 1 2 3 4 5
squares := square(nums) // <-chan int: 1 4 9 16 25
for v := range squares {
fmt.Println(v)
}
Every stage follows the same contract: read from in until it's closed,
send transformed values on out, and close out when done — passing the
"stream is finished" signal down the line. Goroutines in each stage run
concurrently, so the pipeline streams: square is squaring 2 while
generate is producing 3.
Your exercise is to build the square stage. Get the contract right —
especially the close — and it will snap onto any upstream producer.
Practice
1. Pipeline Stage
IntermediateImplement
square, a pipeline stage that squares every value flowing through it:func square(in <-chan int) <-chan int- Return a channel that yields
v * vfor everyvreceived fromin, in the same order. - Keep reading until
inis closed and drained, then close your output channel — that's how the end-of-stream signal travels down a pipeline. - The stage must stream: create the output channel, do the work in a goroutine, and return the channel immediately (don't collect everything into a slice first).
When it works,
squarecomposes with thegeneratefunction from the previous lesson:for v := range square(generate(4)) { fmt.Println(v) // 1 4 9 16 }If
inis closed with nothing in it, the output is simply closed with nothing in it.package main import "fmt" // square returns a channel that yields v*v for every v received from in, // closing the returned channel once in is closed and drained. func square(in <-chan int) <-chan int { // TODO: range over in inside a goroutine, send squares on a new // channel, and close it when in runs dry. out := make(chan int) close(out) return out } func main() { nums := make(chan int, 3) nums <- 2 nums <- 3 nums <- 4 close(nums) for v := range square(nums) { fmt.Println(v) } }Sign in to write and run your solution in the browser.
Sign in for hints and the solution.
- Return a channel that yields