Channels
Stone arches bound by whispered enchantment. Drop an orb in here to pluck it out there.
package main
import (
"fmt"
"time"
)
type orb struct{
id int
}
func main() {
var (
portal = make(chan orb, 1)
tally int
)
go send(portal, 4, 1200)
for range portal {
tally++
}
fmt.Printf("ferried %d orbs through portal\n", tally)
}
// conjure orbs to drop into portal.
func send(out chan<- orb, count int, delay int) {
sleep := time.Duration(delay) * time.Millisecond * 4
for idx := 0; idx < count; idx++ {
time.Sleep(sleep)
orb := orb{
id: idx,
}
out <- orb
}
close(out)
} What you are witnessing
A Go chan T is a typed conduit between goroutines. Send on one side with ch <- v.
Receive on the other with v := <-ch. The runtime hands the value across safely, in the order it
was sent including all memory ordering and synchronisation you would otherwise have to braid by hand. In the
workshop above there is a pair of stone arch portals. Drop an orb into the base portal and watch it rematerialise
at the cliff portal. No climbing required.
Turn the portal off and the spell collapses to its mundane equivalent. The sender must hand each orb over in person, by means of climbing up the cliffside and back down to summon the next. During that travel, the receiver sits idle. Such is the visible cost of having no coordination at all.
Buffers
Slide the buffer dial and watch the row of slots between the arches change. A channel made with make(chan T)
is unbuffered. Every send waits for a paired receive (a rendezvous), the spark at the midpoint above
pulses once for each handoff. With make(chan T, N) you carve out N slots inside the conduit so
the sender can run up to N orbs ahead of the receiver before its next send blocks. The receiver blocks when the
conduit runs dry. Watch the slots fill when the sender outpaces the receiver. The sender will pause when they are all taken
and the receiver yawns when none are full.
- Unbuffered (
buffer = 0). Lockstep. Use it when the send itself is the synchronisation. The sender needs to know the receiver has taken the value before moving on. - Buffered (
buffer = N). Small elasticity to smooth bursts. The buffer absorbs short producer / consumer mismatches without either side stalling.
Why a wizard cares
Channels are Go's pithy answer to "share memory by communicating, instead of communicating by sharing memory".
A channel send happens before its paired receive. This is a formal guarantee in the memory model, not just a
polite suggestion so the receiver sees every write the sender made before the send. You get safe handoff, backpressure
and (with select) the ability to wait on multiple conduits at once. All without an explicit
lock.
Shape of the spell
- Direction matters. Declare args as
chan<- Tfor send only and<-chan Tfor receive only when you can. The compiler will catch ferrying mistakes that would otherwise be a race. - The sender closes; the receiver doesn't.
close(ch)signals "no more sends". Afor v := range chloop on the receive side exits cleanly when the channel is closed and drained. Closing a channel the sender doesn't own or closing twice will panic. selectis yourswitchfor conduits. Wait on any of several channels without busy polling.- A channel is a value. Pass it by arg. Do not stash it in a package level var unless you really mean to share one conduit across the project.
Pitfalls
- Deadlock on unbuffered send. Send on a channel that no goroutine is receiving from and the sender will hang forever. The runtime will spot fatal deadlocks and crash but it cannot spot more subtle ones.
- Buffers are not a fix for slow consumers. If the receiver is always slower than the sender, a buffer only delays the blocking. Prefer to reach for a different shape instead of larger number.
- Receiving from a closed channel never blocks. It returns the
zero value (and
ok == falsein the comma ok form). Forgetting this turns "no more orbs" into "an infinite stream of empty orbs". - Sending to closed channel will panic. Always close from the side that knows there are no more sends.
- Goroutine leaks. A goroutine blocked on send or receive
that will never be paired stays in memory forever. Pair channels with a
context.Context/quit channelwhen lifetime is in doubt.
Reagents required: built in chan primitive, so no import. Mountain
boots optional but recommended if you intend to turn the portal off.