Skip to article
ALGORITHMICSConcurrency
Concurrency6 min read

CSP and Channels

Don't communicate by sharing memory; share memory by communicating.


Go’s slogan is the whole idea:

Do not communicate by sharing memory; share memory by communicating.

Instead of two goroutines both touching a variable behind a lock, one sends the value to the other. Ownership moves with the message, so at any moment exactly one of them has it.

A channel is a typed pipe

results := make(chan int)
go func() {
results <- expensiveWork() // send
}()
value := <-results // receive; blocks until something arrives

No mutex, no shared variable, no visibility question. The channel handles all three.

Buffered or not is a real decision

Channel

make(chan T)

sender
no slots
receiver

A send does not complete until a receiver takes the value, so the two goroutines meet. That makes the channel a synchronisation point as well as a pipe — after the handoff, both know the other reached this line.

Unbuffered (make(chan int)) — a send blocks until a receiver takes it. The two goroutines rendezvous, so the channel is a synchronisation point as well as a pipe: after the handoff, each knows the other reached that line.

Buffered (make(chan int, 3)) — the sender can run up to three ahead. That decouples their speeds, and the capacity is your backpressure budget.

select is what makes it a model rather than a queue

Wait on several channels at once, and act on whichever is ready first:

select {
case job := <-work:
process(job)
case <-done:
return // shut down
case <-time.After(5 * time.Second):
log.Println("idle for five seconds")
}

Timeouts, cancellation and fan-in all fall out of this one construct. Without it, channels are just queues; with it, they are a way to structure a program.

The mistakes

Goroutine leaks. A goroutine blocked forever on a channel nobody will use never returns, and its stack and captured variables are never freed. This is the Go equivalent of a memory leak and it is the most common bug in channel-heavy code. Always give a blocked goroutine an exit — a done channel, or a context.Context.

Deadlock by channel. Two goroutines each waiting to receive from the other is the same cycle as any deadlock. Go’s runtime detects the case where all goroutines are blocked and panics with a clear message; partial deadlocks it cannot see.

CSP and actors

Both avoid shared memory by passing messages. The difference is what has a name:

CSPActors
Named thingthe channelthe actor
Processesanonymoushave addresses
Default sendsynchronous (unbuffered)asynchronous
Bufferingexplicit, at the channela mailbox, always present
Across a networkawkward — rendezvous needs both endsnatural

CSP suits pipelines inside one process. Actors suit distributed systems, where an address survives a machine boundary and a rendezvous does not.