gonjure
A grimoire of go incantations

← back to the grimoire

Channels

Stone arches bound by whispered enchantment. Drop an orb in here to pluck it out there.

grimoire fragment · main.go
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)
}
The whispering portals
z z z channel slots buffer = 1 sender 1.0× · receiver 2.0×
Dials & sigils

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<- T for send only and <-chan T for 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". A for v := range ch loop 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.
  • select is your switch for 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 == false in 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 channel when 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.