Programming Fundamentals › Concurrency & Async
Channel
A typed pipe for passing values between concurrent tasks.
Also known as: channel, go channel, mpmc channel
A Channel is a typed conduit that one concurrent task writes to and another reads from — a pipe with a type. Instead of sharing memory and protecting it with a lock, tasks hand values to each other over a channel. Send and receive are the only operations; the channel carries the data and the synchronisation together.
ch := make(chan int) // unbuffered
go func() { ch <- 42 }() // send
v := <-ch // receive (blocks until a value arrives)
Channels come in two flavours:
- Unbuffered — a send blocks until a receiver is ready, and a receive blocks until a sender arrives. This rendezvous is the synchronisation: the two tasks meet at the channel.
- Buffered — the channel holds up to N values; a send blocks only when the buffer is full, a receive only when it’s empty. The buffer decouples the two tasks in time.
Many languages also offer a select (or equivalent) to wait on several channels at once, which is how you build timeouts and multiplexing.
The classic mistakes:
- Sending on a closed channel. In Go this panics. Only the sender should close a channel, and only when it won’t send again — a receiver closing it, or a second sender closing it, causes crashes on the next send.
- Deadlock on an unbuffered channel. If a task sends but nobody ever receives (or vice versa), it blocks forever. Buffered channels reduce this but don’t remove it.
- Leaking goroutines or tasks. A task blocked on a channel that will never receive stays alive forever. Always make sure every send has a receiver, or provide a cancellation path.
- Unbounded buffering. A large buffer looks like it solves throughput, but it just postpones the problem and hides missing backpressure. Bound it.
- Using channels where a lock is simpler. Channels are excellent for passing ownership of data between tasks; a small shared counter is still often clearer with a mutex.
A common guideline captures the trade-off: share memory by communicating, don’t communicate by sharing memory. Channels make the handoff of data explicit, which is why they’re central to Go, and appear as mpsc/crossbeam in Rust and as queues in other runtimes. They pair naturally with green threads and the actor model.