Channels
A channel carries values between goroutines, and the waiting it does is what keeps the two of them in step.
ch := make(chan T) // ch <- value sends; value := <-ch receivesA goroutine is a piece of work running alongside the rest of your program. Two goroutines often need to hand a value from one to the other. A channel is the pipe they use.
Build one with make. ch := make(chan string) gives a channel that carries strings, and only strings. Send with ch <- "hello". Receive with msg := <-ch. The arrow always points the way the value travels.
The waiting is the point
A channel made without a size is unbuffered. A send on it does not finish until some other goroutine receives. A receive does not finish until some other goroutine sends. The two goroutines meet at that moment.
It is like handing someone a note. You hold it out. You stand there until they take it. When they do, you both know the handover happened.
That is why a channel is a synchronisation tool and not just a queue. It moves a value, and it orders two goroutines, in one step.
What trips people up
A send with nobody to receive it waits forever. If every goroutine in the program is waiting, the runtime prints fatal error: all goroutines are asleep - deadlock! and stops. In practice that means you sent on a channel from the same goroutine that was going to receive from it. Start the other side with go first.
A channel declared with var but never passed to make is nil. Sending to it or receiving from it blocks forever, with no error message at all.
Example
package main
import "fmt"
// greet sends one string on ch, then returns.
func greet(ch chan string) {
ch <- "hello from the other goroutine"
}
func main() {
// make builds a working channel. No size means unbuffered: a send
// waits until some receive is ready, and a receive waits for a send.
ch := make(chan string)
go greet(ch) // greet now runs alongside main
msg := <-ch // main stops here until greet sends
fmt.Println(msg)
// The same blocking is how one goroutine waits for another to finish.
done := make(chan bool)
go func() {
fmt.Println("worker did its job")
done <- true // parks until main receives
}()
<-done // receive the value and throw it away
fmt.Println("main finished")
}
// Output:
// hello from the other goroutine
// worker did its job
// main finishedWhen to use it
- Return the result of a slow download from the goroutine that performed it.
- Make main wait for a background task to finish before the program exits.
- Hand each incoming request to a long-running worker goroutine.
- Give one goroutine sole ownership of a counter and have others send it updates.
More examples
Strict turns with two unbuffered channels
Two goroutines pass a number back and forth. Because neither channel has a buffer, neither side can run ahead: every print happens in a fixed order, which is why the output is the same on every run. Reach for this when you want to prove to yourself that an unbuffered send really does park until the other side receives. The same shape appears in real code as a request channel plus a reply channel, where one goroutine owns some state and others ask it questions.
package main
import "fmt"
func main() {
toPong := make(chan int)
toPing := make(chan int)
// Both channels are unbuffered, so every send waits for the other side
// to receive. The two goroutines are forced to take strict turns.
go func() {
for i := 0; i < 3; i++ {
n := <-toPong
fmt.Println("pong got", n)
toPing <- n + 1
}
}()
n := 0
for i := 0; i < 3; i++ {
toPong <- n
n = <-toPing
fmt.Println("ping got", n)
}
}
// Output:
// pong got 0
// ping got 1
// pong got 1
// ping got 2
// pong got 2
// ping got 3Carrying a value and an error on one channel
A goroutine cannot return a value to its caller the way an ordinary function call does, and it cannot return an error either. The usual answer is a channel of a small struct holding both. You need this the moment a background task can fail: a download, a query, a file read. Without it you end up with one channel for answers and another for errors, and the receiver has to guess which one to read.
package main
import (
"errors"
"fmt"
)
// result bundles what a goroutine produced with what went wrong, so one
// channel can carry both.
type result struct {
name string
bytes int
err error
}
func measure(name, text string, out chan<- result) {
if text == "" {
out <- result{name: name, err: errors.New("no text")}
return
}
out <- result{name: name, bytes: len(text)}
}
func main() {
out := make(chan result)
go measure("intro", "hello", out)
r := <-out // blocks until measure sends
if r.err != nil {
fmt.Println("failed:", r.err)
return
}
fmt.Printf("%s: %d bytes\n", r.name, r.bytes)
}
// Output:
// intro: 5 bytesThe same operations written short and long
Three pairs of forms that mean the same thing, so you recognise either one in somebody else's code. <code>make(chan int)</code> and <code>make(chan int, 0)</code> both give an unbuffered channel. A receive can be a whole statement on its own, which discards the value, or it can be stored. And <code>v := <-a</code> is the short declaration form of <code>var v int = <-a</code>. The bare <code><-a</code> form is common in shutdown code, where the value carries no information and only the arrival matters.
package main
import "fmt"
func main() {
// These two lines build the same thing. No capacity, and a capacity of
// zero, both mean unbuffered.
a := make(chan int)
b := make(chan int, 0)
fmt.Println("same capacity:", cap(a) == cap(b))
go func() {
a <- 7
a <- 8
}()
<-a // a receive can stand alone as a statement; the value is dropped
v := <-a // short form; "var v int = <-a" is the same line written out
fmt.Println("kept:", v)
}
// Output:
// same capacity: true
// kept: 8
Discussion