Go path · Many things at once · Topic 10 of 13

Channels and select

Not started

A channel is a typed conduit that one goroutine sends values into and another receives them from. It does two jobs at once: it moves data, and it synchronises, because a send or receive can wait for the other side. That gives Go its slogan for concurrent code: don't communicate by sharing memory; share memory by communicating. A value that has been sent belongs to the receiver now, so no lock is needed around it.

Why it matters: Channels let goroutines hand work and results to each other with no shared variables. Knowing exactly when a send or receive blocks, and what close means, is the difference between a clean pipeline and a deadlock.

  1. 1Share memory by communicating

    Instead of several goroutines guarding one variable with a lock, pass the value over a channel so only one goroutine owns it at a time.

  2. 2Unbuffered means rendezvous

    A send on an unbuffered channel waits until a receiver takes the value. A buffered channel lets sends go ahead until the buffer is full.

  3. 3Close is a broadcast from the sender

    After close, receivers get the remaining buffered values and then the zero value with ok false. Sending on a closed channel panics.

  4. 4select waits on several at once

    It runs whichever case is ready, picks at random if several are, and with a default case never blocks.

1

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 close panic.
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, on ctx.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 nil and 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.

2

Check yourself

3 questions. Get them all right to finish this topic.

  1. 1.What happens when you send on a closed channel?
  2. 2.ch is buffered, holds one value, and is then closed. What do two receives return?
  3. 3.In a select, why might you set a channel variable to nil?

0 of 3 right so far. Get all three to finish this topic.