Channels

Go · Practical · 14 min

Channels are Go's primary way for goroutines to communicate and synchronise.

A channel is a typed conduit between goroutines: ch := make(chan int). ch <- v sends, v := <-ch receives. Go's motto: share memory by communicating.

An unbuffered channel synchronises: a send blocks until a receiver takes the value, so it doubles as a handshake. A buffered channel, make(chan int, 10), lets sends proceed until the buffer is full and receives proceed while it has values. Buffers smooth out bursts; they don't fix a design where nobody receives.

Closing: close(ch) tells receivers no more values are coming. After close, receives drain any buffered values and then return the zero value immediately; v, ok := <-ch gives ok == false once closed and empty. for v := range ch loops until the channel is closed and drained — the standard consumer loop. Rules:

  • Only the sender closes, and only when receivers need to know it's done. Not every channel needs closing.
  • Sending on a closed channel panics; closing twice panics.
  • A nil channel blocks forever on both send and receive.

Directional types document intent and are enforced by the compiler: chan<- int is send-only, <-chan int receive-only. Functions that produce values typically return <-chan T.

If every goroutine is blocked on channel operations, the runtime aborts with fatal error: all goroutines are asleep - deadlock! — the most common first channel bug, usually a send nobody receives.

Example

package main

import "fmt"

// generate returns a receive-only channel and closes it when done.
func generate(n int) <-chan int {
	out := make(chan int)
	go func() {
		defer close(out)
		for i := range n {
			out <- i * i
		}
	}()
	return out
}

func main() {
	sum := 0
	for v := range generate(5) { // ends when out is closed
		sum += v
	}
	fmt.Println(sum) // 30

	buf := make(chan string, 2)
	buf <- "a"
	buf <- "b" // doesn't block: buffer has room
	close(buf)
	v, ok := <-buf
	fmt.Println(v, ok, len(buf)) // a true 1
}

Remember

  • Unbuffered channels hand off and synchronise; buffered ones queue
  • The sender closes; receivers use range or v, ok := <-ch
  • Use directional types <-chan T / chan<- T in signatures

Watch out for

  • Closing a channel from the receiver side or from several senders — causes send-on-closed panics.

Check yourself

  1. What happens when you receive from a closed, empty `chan int`?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: go.dev/tour/concurrency/2