SECTION 6.2

Channels: Communication

Channels are typed conduits for sending data between goroutines.

Creating and Using

ch := make(chan string)    // unbuffered channel of strings

// Send (blocks until someone receives)
go func() {
    ch <- "hello from goroutine"
}()

// Receive (blocks until something is sent)
msg := <-ch
fmt.Println(msg) // "hello from goroutine"

Unbuffered Channels

Unbuffered channels synchronize sender and receiver. The sender blocks until someone reads:

ch := make(chan int) // unbuffered

go func() {
    ch <- 42  // blocks here until main reads
    fmt.Println("sent!")
}()

val := <-ch // unblocks the sender
fmt.Println(val) // 42

Use unbuffered channels when you need synchronization — "don't proceed until the other side is ready."

Buffered Channels

Buffered channels hold up to N values. Sends only block when full:

ch := make(chan string, 3) // buffer of 3

ch <- "first"   // doesn't block (buffer has space)
ch <- "second"  // doesn't block
ch <- "third"   // doesn't block
// ch <- "fourth" // WOULD block — buffer full

fmt.Println(<-ch) // "first"

Use buffered channels when the producer and consumer run at different speeds.

The first two cases succeed because the buffer has room for every send. The remaining cases deadlock: with no receiver running, the goroutine fills the buffer and then blocks on the next send forever. Go's runtime detects that every goroutine is blocked and panics with "all goroutines are asleep - deadlock". The unbuffered case (buf 0) deadlocks immediately because an unbuffered channel needs a simultaneous receiver, not a buffer.

Closing Channels

ch := make(chan int, 5)
for i := 0; i < 5; i++ {
    ch <- i
}
close(ch) // signals "no more values"

// Range reads until channel is closed
for val := range ch {
    fmt.Println(val)
}
// prints 0, 1, 2, 3, 4 then exits the loop

Only the sender closes. Never close from the receiver side. Closing a closed channel panics.

ch := make(chan int, 1)
ch <- 42
close(ch)

a := <-ch b := <-ch c := <-ch fmt.Println(a, b, c)

After a channel is closed, you can keep reading from it forever — every read returns the zero value of the element type (0 for int, "" for string, nil for pointers). No error, no panic, no signal. To distinguish "real value" from "channel drained and closed", use the comma-ok form: v, ok := <-ch. ok is false once you've drained a closed channel.

Direction-Restricted Channels

// Send-only channel (in function signature)
func producer(out chan<- string) {
    out <- "data"
}

// Receive-only channel
func consumer(in <-chan string) {
    msg := <-in
    fmt.Println(msg)
}

This prevents accidentally sending on a receive channel (or vice versa). Use in function signatures for clarity.