If goroutines are the activities of a concurrent Go program,
channels are the connections between them.
A channel is a communication mechanism that lets one goroutine send
values to another goroutine.
Each channel is a conduit for values of a particular type, called the
channel’s element type.
The type of a channel whose elements have type int
is written
chan int
.
To create a channel, we use the built-in make
function:
ch := make(chan int) // ch has type 'chan int'
As with maps, a channel is a reference to the data structure
created by make
.
When we copy a channel or pass
one as an argument to a function, we are copying a reference, so
caller and callee refer to the same data structure.
As with other reference types, the zero value of a channel is nil
.
Two channels of the same type may be compared using ==
.
The comparison is true if both are references to the same channel data structure.
A channel may also be compared to nil
.
A channel has two principal operations, send and
receive, collectively known as communications.
A send statement transmits a value from one goroutine,
through the channel, to another goroutine executing a
corresponding receive expression.
Both operations are written using the <-
operator.
In a send statement, the <-
separates the channel
and value operands. In a receive expression, <-
precedes
the channel operand. A receive expression whose result is not used
is a valid statement.
ch <- x // a send statement x = <-ch // a receive expression in an assignment statement <-ch // a receive statement; result is discarded
Channels support a third operation, close, which sets a flag indicating that no more values will ever be sent on this channel; subsequent attempts to send will panic. Receive operations on a closed channel yield the values that have been sent until no more values are left; any receive operations thereafter complete immediately and yield the zero value of the channel’s element type.
To close a channel, we call the built-in close
function:
close(ch)
A channel created with a simple call to make
is
called an unbuffered channel, but
make
accepts an optional second argument, an integer
called the channel’s capacity.
If the capacity is non-zero, make
creates a buffered channel.
ch = make(chan int) // unbuffered channel ch = make(chan int, 0) // unbuffered channel ch = make(chan int, 3) // buffered channel with capacity 3
We’ll look at unbuffered channels first and buffered channels in Section 8.4.4.
A send operation on an unbuffered channel blocks the sending goroutine until another goroutine executes a corresponding receive on the same channel, at which point the value is transmitted and both goroutines may continue. Conversely, if the receive operation was attempted first, the receiving goroutine is blocked until another goroutine performs a send on the same channel.
Communication over an unbuffered channel causes the sending and receiving goroutines to synchronize. Because of this, unbuffered channels are sometimes called synchronous channels. When a value is sent on an unbuffered channel, the receipt of the value happens before the reawakening of the sending goroutine.
In discussions of concurrency, when we say x happens before y, we don’t mean merely that x occurs earlier in time than y; we mean that it is guaranteed to do so and that all its prior effects, such as updates to variables, are complete and that you may rely on them.
When x neither happens before y nor after y, we say that x is concurrent with y. This doesn’t mean that x and y are necessarily simultaneous, merely that we cannot assume anything about their ordering. As we’ll see in the next chapter, it’s necessary to order certain events during the program’s execution to avoid the problems that arise when two goroutines access the same variable concurrently.
The client program in Section 8.3 copies input to the server in its main goroutine, so the client program terminates as soon as the input stream closes, even if the background goroutine is still working. To make the program wait for the background goroutine to complete before exiting, we use a channel to synchronize the two goroutines:
func main() { conn, err := net.Dial("tcp", "localhost:8000") if err != nil { log.Fatal(err) } done := make(chan struct{}) go func() { io.Copy(os.Stdout, conn) // NOTE: ignoring errors log.Println("done") done <- struct{}{} // signal the main goroutine }() mustCopy(conn, os.Stdin) conn.Close() <-done // wait for background goroutine to finish }
When the user closes the standard input stream,
mustCopy
returns and the main goroutine calls
conn.Close()
, closing both halves of the network connection.
Closing the write half of the connection causes the server to see an
end-of-file condition.
Closing the read half causes the background goroutine’s call to
io.Copy
to return a “read from closed connection” error, which
is why we’ve removed the error logging; Exercise 8.3 suggests
a better solution.
(Notice that the go
statement calls a
literal function, a common construction.)
Before it returns, the background goroutine logs a message,
then sends a value on the done
channel.
The main goroutine waits until it has received this value before
returning.
As a result, the program always logs the
"done"
message before exiting.
Messages sent over channels have two important aspects.
Each message has a value, but sometimes the fact of communication and
the moment at which it occurs are just as important.
We call messages events when we wish to stress this aspect.
When the event carries no additional information, that is, its sole
purpose is synchronization, we’ll emphasize this by using a channel
whose element type is struct{}
, though it’s common to
use a channel of bool
or int
for the same purpose
since done <- 1
is shorter than done <- struct{}{}
.
Exercise 8.3:
In netcat3
, the interface value conn
has the concrete
type *net.TCPConn
, which represents a TCP connection.
A TCP connection consists of two halves that may be closed
independently using its CloseRead
and CloseWrite
methods.
Modify the main goroutine of netcat3
to close only the write
half of the connection so that the program will continue to print the
final echoes from the reverb1
server even after the standard
input has been closed.
(Doing this for the reverb2
server is harder;
see Exercise 8.4.)
Channels can be used to connect goroutines together so that the output of one is the input to another. This is called a pipeline. The program below consists of three goroutines connected by two channels, as shown schematically in Figure 8.1.
The first goroutine, counter, generates the integers 0, 1, 2, ..., and sends them over a channel to the second goroutine, squarer, which receives each value, squares it, and sends the result over another channel to the third goroutine, printer, which receives the squared values and prints them. For clarity of this example, we have intentionally chosen very simple functions, though of course they are too computationally trivial to warrant their own goroutines in a realistic program.
func main() { naturals := make(chan int) squares := make(chan int) // Counter go func() { for x := 0; ; x++ { naturals <- x } }() // Squarer go func() { for { x := <-naturals squares <- x * x } }() // Printer (in main goroutine) for { fmt.Println(<-squares) } }
As you might expect, the program prints the infinite series of squares 0, 1, 4, 9, and so on. Pipelines like this may be found in long-running server programs where channels are used for lifelong communication between goroutines containing infinite loops. But what if we want to send only a finite number of values through the pipeline?
If the sender knows that no further values will ever be sent on a
channel, it is useful to communicate this fact to the receiver
goroutines so that they can stop waiting.
This is accomplished by closing the channel using
the built-in close
function:
close(naturals)
After a channel has been closed, any further send operations on it will
panic.
After the closed channel has been drained,
that is, after the last
sent element has been received, all subsequent receive operations will
proceed without blocking but will yield a zero value.
Closing the naturals
channel above would cause the squarer’s
loop to spin as it receives a never-ending stream of zero values,
and to send these zeros to the printer.
There is no way to test directly whether a channel has been closed,
but there is a variant of the receive operation that produces two
results: the received channel element, plus a boolean value,
conventionally called ok
, which is true
for a successful receive and
false
for a receive on a closed and drained channel.
Using this feature, we can modify the squarer’s loop to stop when the
naturals
channel is drained and close the squares
channel in turn.
// Squarer go func() { for { x, ok := <-naturals if !ok { break // channel was closed and drained } squares <- x * x } close(squares) }()
Because the syntax above is clumsy and this pattern is common, the
language lets us use a range
loop to iterate over channels too.
This is a more convenient syntax for receiving all the values
sent on a channel and terminating the loop after the last one.
In the pipeline below, when the counter goroutine finishes its loop
after 100 elements, it closes the naturals
channel, causing the
squarer to finish its loop and close the squares
channel.
(In a more complex program, it might make sense for the counter and
squarer functions to defer the calls to close
at the outset.)
Finally, the main goroutine finishes its loop and the program exits.
func main() { naturals := make(chan int) squares := make(chan int) // Counter go func() { for x := 0; x < 100; x++ { naturals <- x } close(naturals) }() // Squarer go func() { for x := range naturals { squares <- x * x } close(squares) }() // Printer (in main goroutine) for x := range squares { fmt.Println(x) } }
You needn’t close every channel when you’ve finished with it.
It’s only necessary to close a channel when it is important to tell
the receiving goroutines that all data have been sent.
A channel that the garbage collector determines to be unreachable
will have its resources reclaimed whether or not it is closed.
(Don’t confuse this with the close operation for open files.
It is important to call the Close
method on every file
when you’ve finished with it.)
Attempting to close an already-closed channel causes a panic, as does closing a nil channel. Closing channels has another use as a broadcast mechanism, which we’ll cover in Section 8.9.
As programs grow, it is natural to break up large functions into
smaller pieces. Our previous example used three goroutines,
communicating over two channels, which were local variables of main
.
The program naturally divides into three functions:
func counter(out chan int) func squarer(out, in chan int) func printer(in chan int)
The squarer
function, sitting in the middle of the pipeline, takes
two parameters, the input channel and the output channel. Both have
the same type, but their intended uses are opposite: in
is only to
be received from, and out
is only to be sent to. The names in
and
out
convey this intention, but still, nothing prevents
squarer
from sending to in
or receiving from out
.
This arrangement is typical. When a channel is supplied as a function parameter, it is nearly always with the intent that it be used exclusively for sending or exclusively for receiving.
To document this intent and prevent misuse, the Go type system
provides unidirectional channel types that expose only one or
the other of the send and receive operations.
The type chan<- int
, a send-only channel of
int
, allows sends but not receives.
Conversely, the type <-chan int
, a receive-only
channel of int
, allows receives but not sends.
(The position of the <-
arrow relative to the chan
keyword is a mnemonic.)
Violations of this discipline are detected at compile time.
Since the close
operation asserts that no more sends will occur on a
channel, only the sending goroutine is in a position to call it, and
for this reason it is a compile-time error to attempt to close a
receive-only channel.
Here’s the squaring pipeline once more, this time with unidirectional channel types:
func counter(out chan<- int) { for x := 0; x < 100; x++ { out <- x } close(out) } func squarer(out chan<- int, in <-chan int) { for v := range in { out <- v * v } close(out) } func printer(in <-chan int) { for v := range in { fmt.Println(v) } } func main() { naturals := make(chan int) squares := make(chan int) go counter(naturals) go squarer(squares, naturals) printer(squares) }
The call counter(naturals)
implicitly converts naturals
, a value
of type chan int
, to the type of the parameter, chan<- int
.
The printer(squares)
call does a similar implicit conversion to
<-chan int
.
Conversions from bidirectional to unidirectional channel types are
permitted in any assignment.
There is no going back, however: once you have a value of a
unidirectional type such as chan<- int
, there is no way to
obtain from it a value of type chan int
that refers to the same
channel data structure.
A buffered channel has a queue of elements.
The queue’s maximum size is determined when it is created, by the
capacity argument to make
.
The statement below creates a buffered channel capable of holding
three string
values.
Figure 8.2 is a graphical representation of
ch
and the channel to which it refers.
ch = make(chan string, 3)
Figure 8.2. An empty buffered channel.
A send operation on a buffered channel inserts an element at the back of the queue, and a receive operation removes an element from the front. If the channel is full, the send operation blocks its goroutine until space is made available by another goroutine’s receive. Conversely, if the channel is empty, a receive operation blocks until a value is sent by another goroutine.
We can send up to three values on this channel without the goroutine blocking:
ch <- "A" ch <- "B" ch <- "C"
At this point, the channel is full (Figure 8.3), and a fourth send statement would block.
Figure 8.3. A full buffered channel.
If we receive one value,
fmt.Println(<-ch) // "A"
the channel is neither full nor empty (Figure 8.4), so either a send operation or a receive operation could proceed without blocking. In this way, the channel’s buffer decouples the sending and receiving goroutines.
Figure 8.4. A partially full buffered channel.
In the unlikely event that a program needs to know the channel’s buffer capacity,
it can be obtained by calling the built-in cap
function:
fmt.Println(cap(ch)) // "3"
When applied to a channel, the built-in len
function
returns the number of elements currently buffered.
Since in a concurrent program this information is likely to be stale
as soon as it is retrieved, its value is limited, but it could
conceivably be useful during fault diagnosis or performance
optimization.
fmt.Println(len(ch)) // "2"
After two more receive operations the channel is empty again, and a fourth would block:
fmt.Println(<-ch) // "B" fmt.Println(<-ch) // "C"
In this example, the send and receive operations were all performed by the same goroutine, but in real programs they are usually executed by different goroutines. Novices are sometimes tempted to use buffered channels within a single goroutine as a queue, lured by their pleasingly simple syntax, but this is a mistake. Channels are deeply connected to goroutine scheduling, and without another goroutine receiving from the channel, a sender—and perhaps the whole program—risks becoming blocked forever. If all you need is a simple queue, make one using a slice.
The example below shows an application of a buffered channel.
It makes parallel requests to three mirrors, that is, equivalent
but geographically distributed servers.
It sends their responses over a buffered channel, then receives and
returns only the first response, which is the quickest one to arrive.
Thus mirroredQuery
returns a result even before the two
slower servers have responded.
(Incidentally, it’s quite normal for several goroutines to send values
to the same channel concurrently, as in this example, or to receive
from the same channel.)
func mirroredQuery() string { responses := make(chan string, 3) go func() { responses <- request("asia.gopl.io") }() go func() { responses <- request("europe.gopl.io") }() go func() { responses <- request("americas.gopl.io") }() return <-responses // return the quickest response } func request(hostname string) (response string) { /* ... */ }
Had we used an unbuffered channel, the two slower goroutines would have gotten stuck trying to send their responses on a channel from which no goroutine will ever receive. This situation, called a goroutine leak, would be a bug. Unlike garbage variables, leaked goroutines are not automatically collected, so it is important to make sure that goroutines terminate themselves when no longer needed.
The choice between unbuffered and buffered channels, and the choice of a buffered channel’s capacity, may both affect the correctness of a program. Unbuffered channels give stronger synchronization guarantees because every send operation is synchronized with its corresponding receive; with buffered channels, these operations are decoupled. Also, when we know an upper bound on the number of values that will be sent on a channel, it’s not unusual to create a buffered channel of that size and perform all the sends before the first value is received. Failure to allocate sufficient buffer capacity would cause the program to deadlock.
Channel buffering may also affect program performance. Imagine three cooks in a cake shop, one baking, one icing, and one inscribing each cake before passing it on to the next cook in the assembly line. In a kitchen with little space, each cook that has finished a cake must wait for the next cook to become ready to accept it; this rendezvous is analogous to communication over an unbuffered channel.
If there is space for one cake between each cook, a cook may place a finished cake there and immediately start work on the next; this is analogous to a buffered channel with capacity 1. So long as the cooks work at about the same rate on average, most of these handovers proceed quickly, smoothing out transient differences in their respective rates. More space between cooks—larger buffers—can smooth out bigger transient variations in their rates without stalling the assembly line, such as happens when one cook takes a short break, then later rushes to catch up.
On the other hand, if an earlier stage of the assembly line is consistently faster than the following stage, the buffer between them will spend most of its time full. Conversely, if the later stage is faster, the buffer will usually be empty. A buffer provides no benefit in this case.
The assembly line metaphor is a useful one for channels and goroutines. For example, if the second stage is more elaborate, a single cook may not be able to keep up with the supply from the first cook or meet the demand from the third. To solve the problem, we could hire another cook to help the second, performing the same task but working independently. This is analogous to creating another goroutine communicating over the same channels.
We don’t have space to show it here, but the gopl.io/ch8/cake
package simulates this cake shop, with several parameters you can vary.
It includes benchmarks (§11.4) for a few of
the scenarios described above.