Infrastructure Go daemon
SECTION 6.2

Channels: Communication

Channels are typed conduits for sending data between goroutines.

Workers send results, a coordinator closes the channel, and a consumer ranges until close
Ownership rule: the goroutine that can prove no more sends will occur closes the channel. Receivers observe closure; they normally do not initiate it.
1 · Before you read — commit to an answer
ch := make(chan int, 1)
ch <- 42
close(ch)

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

Commit to all three values before reading on.

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

2 · Worked example — read every step
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.

That pretest at the top is this rule's flip side: 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.

3 · Fill the gaps
Producer fills, consumer drains — how does the loop know to stop?
results := make(, 3)

go func() { for _, name := range pods { results <- name } // signal: no more values }()

for r := { fmt.Println(r) }

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.

4 · From scratch — this feeds your review queue