Channels
Last updated
Was this helpful?
Last updated
Was this helpful?
Channels are the pipes that connect concurrent goroutines. You can send values into channels from one goroutine and receive those values into another goroutine.
Go provides chan
keyword to create a channel. A channel can transport data of only one data type. No other data types are allowed to be transported from that channel.
You can send and receive values with the channel operator, <-
.
A channel is a first-class value that can be allocated and passed around like any other.
By default, sends and receives block until the other side is ready. This allows goroutines to synchronize without explicit locks or condition variables.
All the above channel operations are blocking by default. Channel operations are also blocking in nature. When some data is written to the channel, goroutine is blocked until some other goroutine reads it from that channel. At the same time channel operations tell the scheduler to schedule another goroutine, that’s why a program doesn’t block forever on the same goroutine.
Like array, slice and map, each channel type has an element type. A channel can only transfer values of the element type of the channel.Channel types can be bi-directional or single-directional. Assume T
is an arbitrary type,
chan T
denotes a bidirectional channel type. Compilers allow both receiving values from and sending values to bidirectional channels.
chan<- T
denotes a send-only channel type. Compilers don't allow receiving values from send-only channels.
<-chan T
denotes a receive-only channel type. Compilers don't allow sending values to receive-only channels.
Values of bidirectional channel type chan T
can be implicitly converted to both send-only type chan<- T
and receive-only type <-chan T
, but not vice versa (even if explicitly).
Along with transferring values (through channels), the ownership of some values may also be transferred between goroutines. When a goroutine sends a value to a channel, we can view the goroutine releases the ownership of some values. When a goroutine receives a value from a channel, we can view the goroutine acquires the ownerships of some values.
There are five channel specified operations. Assume the channel is ch
, their syntax and function calls of these operations are listed here.
Close the channel by using the following function call
where close
is a built-in function. The argument of a close
function call must be a channel value, and the channel ch
must not be a receive-only channel.
Send a value, v
, to the channel by using the following syntax
where v
must be a value which is assignable to the element type of channel ch
, and the channel ch
must not be a receive-only channel. Note that here <-
is a channel-send operator.
Receive a value from the channel by using the following syntax
A channel receive operation always returns at least one result, which is a value of the element type of the channel, and the channel ch
must not be a send-only channel. Note that here <-
is a channel-receive operator. Yes, its representation is the same as a channel-send operator.
For most scenarios, a channel receive operation is viewed as a single-value expression. However, when a channel operation is used as the only source value expression in an assignment, it can result a second optional untyped boolean value and become a multi-value expression. The untyped boolean value indicates whether or not the first result is sent before the channel is closed. (Below we will learn that we can receive unlimited number of values from a closed channel.)
Two channel receive operations which are used as source values in assignments:
Query the value buffer capacity of the channel by using the following function call
where cap
is a built-in function which has ever been introduced in containers in Go. The return result of a cap
function call is an int
value.
Query the current number of values in the value buffer (or the length) of the channel by using the following function call
where len
is a built-in function which also has ever been introduced before. The return value of a len
function call is an int
value. The result length is number of elements which have already been sent successfully to the queried channel but haven't been received (taken out) yet.
Each value written to a channel can only be read once. If multiple goroutines are reading from the same channel, a value written to the channel will only be read by one of them.
All the just introduced channel operations are already synchronized, so no further synchronizations are needed to safely perform these operations.
To make the explanations for channel operations simple and clear, in the remaining of this article, channels will be classified into three categories:
nil
channels.
non-nil but closed channels.
not-closed non-nil channels.
The following table simply summarizes the behaviors for all kinds of operations applying on nil, closed and not-closed non-nil channels.
Close
panic
panic
succeed to close (C)
Send Value To
block for ever
panic
block or succeed to send (B)
Receive Value From
block forever
never block (D)
block or succeed to receive (A)
By default channels are unbuffered. Every write to an open, unbuffered channel causes the writing goroutine to pause until another goroutine reads from the same channel. Likewise, a read from an open, unbuffered channel causes the reading goroutine to pause until another goroutine writes to the same channel. This means you cannot write to or read from an unbuffered channel without at least two concurrently running goroutines.
An unbuffered channel is used to perform synchronous communication between goroutines; a buffered channel is used for perform asynchronous communication. Channels can be buffered. Provide the buffer length
as the second argument to make to initialise a buffered channel:
Sends to a buffered channel block only when the buffer is full. Receives block when the buffer is empty.
Buffered channels are useful when you know how many goroutines you have launched, want to limit the number of goroutines you will launch, or want to limit the amount of work that is queued up.
Buffered channels work great when you want to either gather data back from a set of goroutines that you have launched or when you want to limit concurrent usage.
A sender can close a channel to indicate that no more values will be sent.
Receivers can test whether a channel has been closed by assigning a second parameter to the receive expression:
ok
is false
if there are no more values to receive and the channel is closed.
Note: Only the sender should close a channel, never the receiver. Sending on a closed channel will cause a panic.
Channels aren't like files; you don't usually need to close them. Closing is only necessary when the receiver must be told there are no more values coming, such as to terminate a range loop.
A channel can be closed so that no more data can be sent through it. Receiver goroutine can find out the state of the channel using val, ok := <- channel
syntax where ok
is true
if the channel is open or read operations can be performed and false
if the channel is closed and no more read operations can be performed.
Once a channel is closed, any attempts to write to the channel or close the channel again will panic. Interestingly, attempting to read from a closed channel always succeeds. If the channel is buffered and there are values that haven’t been read yet, they will be returned in order. If the channel is unbuffered or the buffered channel has no more values, the zero value for the channel’s type is returned.
for
-loop for channelsThe loop for i := range c
receives values from the channel repeatedly until it is closed.
Using buffered channels and for range
, we can read from closed channels. Since for closed channels, data lives in the buffer, we can still extract that data.
We can use channels to synchronize execution across goroutines.
So far, we have seen channels which can transmit data from both sides or in simple words, channels on which we can do read and write operations. But we can also create channels which are unidirectional in nature. For example, receive-only channels which allow only read operation on them and send-only channels which allow only to write operation on them.
The unidirectional channel is also created using make
function but with additional arrow syntax.
When using channels as function parameters, you can specify if a channel is meant to only send or receive values.
In general, parallel goroutines have to synchronize: for example, when they need to access or mutate a shared resource such as a slice. Synchronization is enforced with mutexes but not with any channel types (not with buffered channels). Hence, in general, synchronization between parallel goroutines should be achieved via mutexes.
Conversely, in general, concurrent goroutines have to coordinate and orchestrate. For example, if G3 needs to aggregate results from both G1 and G2, G1 and G2 need to signal to G3 that a new intermediate result is available. This coordination falls under the scope of communication—therefore, channels.
It’s important to know whether goroutines are parallel or concurrent because, in general, we need mutexes for parallel goroutines and channels for concurrent ones.
A send to a nil
channel blocks forever
A receive from a nil
channel blocks forever
A send to a closed channel panics
A receive from a closed channel returns the zero value immediately
select
statementselect
is just like switch
without any input argument but it only used for channel operations. The select
statement is used to perform an operation on only one channel out of many, conditionally selected by case
block.
It is the control structure for concurrency in Go, and it elegantly solves a common problem: if you can perform two concurrent operations, which one do you do first? The select keyword allows a goroutine to read from or write to one of a set of multiple channels. Each case in a select is a read or a write to a channel. If a read or write is possible for a case, it is executed along with the body of the case. Like a switch, each case in a select creates its own block.
Unlike a switch statement, where the first case with a match wins, the select statement selects randomly if multiple options are possible.
The select statement is blocking except when it has a default case (we will see that later). Once, one of the case conditions fulfill, it will unblock. So when a case condition fulfills?
If all case statements (channel operations) are blocking then select statement will wait until one of the case statement (its channel operation) unblocks and that case will be executed. If some or all of the channel operations are non-blocking, then one of the non-blocking cases will be chosen randomly and executed immediately. select
на nil
канале выбросит ошибку
The select
statement lets a goroutine wait on multiple communication operations. A select
blocks until one of its cases can run, then it executes that case
. It chooses one at random if multiple are ready.
The default
case in a select
is run if no other case is ready. Use a default case to try a send or receive without blocking:
However, you need to properly handle closed channels. If one of the cases in a select is reading a closed channel, it will always be successful, returning the zero value. Every time that case is selected, you need to check to make sure that the value is valid and skip the case. If reads are spaced out, your program is going to waste a lot of time reading junk values.
As we saw earlier, reading from or writing to a nil channel causes your code to hang forever. While that is bad if it is triggered by a bug, you can use a nil channel to disable a case in a select. When you detect that a channel has been closed, set the channel’s variable to nil. The associated case will no longer run, because the read from the nil channel never returns a value:
Any time you need to limit how long an operation takes in Go, you’ll see a variation on this pattern. We have a select choosing between two cases. The first case takes advantage of the done channel pattern we saw earlier. We use the goroutine closure to assign values to result and err and to close the done channel. If the done channel closes first, the read from done succeeds and the values are returned.
Basic sends and receives on channels are blocking. However, we can use select with a default clause to implement non-blocking sends, receives, and even non-blocking multi-way selects.
Non-Blocking Send
Non-blocking Receive