Channel is a core type in Go. You can think of it as a pipeline through which the concurrent core unit can send or receive data for communication.
Its operator is the arrow <- .
ch <- v // Send the value v to Channel ch
v := <-ch // Receive data from Channel ch and assign data to v
(The direction of the arrow is the flow of data)
Just like map and slice data types, the channel must be created before use:
ch := make(chan int)
Channel Type
The definition format of Channel type is as follows:
ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .
It includes three types of definitions. Optional <- represents the direction of the channel. If there is no specified direction, then the Channel is bidirectional, which can both receive and send data.
chan T // Can receive and send data of type T
chan<- float64 // Can only be used to send data of type float64
<-chan int // Can only be used to receive data of type int
<-Always combine priority with the leftmost type. (The <- operator associates with the leftmost chan possible)
chan<- chan int // Equivalent chan<- (chan int)
chan<- <-chan int // Equivalent chan<- (<-chan int)
<-chan <-chan int // Equivalent <-chan (<-chan int)
chan (<-chan int)
Use make to initialize the Channel, and the capacity can be set:
make(chan int, 100)
Capacity represents the number of elements that the Channel accommodates the most and represents the size of the Channel's cache.
If the capacity is not set, or the capacity is set to 0, it means that the Channel is not cached. Only when the sender and receiver are ready will their communication occur (blocking). If the cache is set, there is a possibility that there will be no blockage. The send will block only after the buffer is full, and the receive will block only after the cache is empty. A nil channel does not communicate.
The built-in close method can be used to close the Channel.
You can receive/send data from/to a channel in multiple goroutines without having to consider additional synchronization measures.
Channels can serve as a first-in-first-out (FIFO) queue, and the order of received data and sent data is consistent.
The receive channel supports multi-valued assignment, such as
v, ok := <-ch
It can be used to check if the Channel has been closed.
send statement
The send statement is used to send data to the Channel, such as ch <- 3.
Its definition is as follows:
SendStmt = Channel "<-" Expression .
Channel = Expression .
Before communication begins, channel and expression must be evaluated first, such as the following (3+4) first calculates 7 and then sends it to the channel.
c := make(chan int)
defer close(c)
go func() { c <- 3 + 4 }()
i := <-c
(i)
The communication is blocked before the send is executed. As mentioned earlier, a channel without cache is executed only after the receiver is ready. If there is a cache and the cache is not full, send will be executed.
Continuing to send data to a channel that has been closed will result in a run-time panic.
Sending data into nil channel will be blocked consistently.
receive operator
<-ch is used to receive data from channel ch, and this expression will be blocked until data can be received.
Receiving data from a nil channel will be blocked all the time.
Receiving data from a closed channel will not be blocked, but will return immediately. After receiving the sent data, it will return the zero value of the element type.
As mentioned earlier, you can use an extra return parameter to check whether the channel is closed.
x, ok := <-ch
x, ok = <-ch
var x, ok = <-ch
If OK is false, it means that the received x is the generated zero value, and the channel is closed or empty.
blocking
By default, sending and receiving will be blocked until the other party is ready. This method can be used to synchronize in gororutine without having to use displayed locks or condition variables.
For example, in the official example, x, y := <-c, <-c will wait for the calculation result to be sent to the channel.
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // send sum to c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // receive from c
(x, y, x+y)
}
Buffered Channels
The second parameter of make specifies the size of the cache: ch := make(chan int, 100).
Through the use of cache, blockage can be avoided as much as possible and the performance of the application can be provided.
Range
The range statement can handle Channels.
func main() {
go func() {
(1 * )
}()
c := make(chan int)
go func() {
for i := 0; i < 10; i = i + 1 {
c <- i
}
close(c)
}()
for i := range c {
(i)
}
("Finished")
}
Range c produces an iterative value sent in the Channel, which it will iterate until the channel is closed. If close(c) is commented out in the above example, the program will keep blocking in the for... range line.
select
The select statement selects a set of possible send operations and receive operations to process. It is similar to switch, but is only used to handle communication operations.
Its case can be a send statement, a receive statement, or a default.
The receive statement can assign a value to one or two variables. It must be a receive operation.
At most, there is a default case that can be placed anywhere in the case list, although we will mostly put it at the end.
import "fmt"
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}
If there are multiple cases to process at the same time, such as multiple channels to receive data at the same time, then Go will pseudo-random select a case (pseudo-random). If there is no case that needs to be processed, default will be selected to process, if the default case exists. If there is no default case, the select statement will block until a case needs to be processed.
It should be noted that the operations on nil channel will be blocked all the time. If there is no default case, only the nil channel's select will be blocked all the time.
The select statement is the same as the switch statement. It is not a loop. It only selects a case to process it. If you want to process the channel all the time, you can add an infinite for loop outside:
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
("quit")
return
}
}
timeout
One of the most important applications of select is timeout processing. Because we mentioned above, if there is no case to be processed, the select statement will keep blocking. At this time, we may need a timeout operation to handle the timeout situation.
In the following example, we will send a data into channel c1 after 2 seconds, but the select is set to 1 second timeout, so we will print out timeout 1 instead of result 1.
import "time"
import "fmt"
func main() {
c1 := make(chan string, 1)
go func() {
( * 2)
c1 <- "result 1"
}()
select {
case res := <-c1:
(res)
case <-( * 1):
("timeout 1")
}
}
In fact, it uses a method, which returns a one-way channel of type <-chan Time, and sends a current time to the returned channel at the specified time.
Timer and Ticker
Let's take a look at two channels about time.
The timer is a timer that represents a single event in the future. You can tell the timer how long you will wait. It provides a Channel, and that Channel provides a time value at that time in the future. In the example below, the second line will block for about 2 seconds and will not continue to execute until the time is up.
timer1 := ( * 2)
<-
("Timer 1 expired")
Of course, if you just want to wait simply, you can use it to achieve it.
You can also use to stop the timer.
timer2 := ()
go func() {
<-
("Timer 2 expired")
}()
stop2 := ()
if stop2 {
("Timer 2 stopped")
}
A ticker is a timer triggered timer that sends an event (current time) to the Channel at an interval, and the Channel receiver can read events from the Channel at a fixed interval. In the following example, the ticker is triggered every 500 milliseconds, and you can observe the output time.
ticker := ( * 500)
go func() {
for t := range {
("Tick at", t)
}
}()
Similar to timer, ticker can also be stopped by the Stop method. Once it stops, the receiver no longer receives data from the channel.
close
The built-in close method can be used to close the channel.
Summarize the receiver operation of sender after channel is closed.
If channel c has been closed, continuing to send data to it will cause panic: send on closed channel:
import "time"
func main() {
go func() {
()
}()
c := make(chan int, 10)
c <- 1
c <- 2
close(c)
c <- 3
}
However, from this closed channel, not only can the sent data be read, but the zero value can also be continuously read:
c := make(chan int, 10)
c <- 1
c <- 2
close(c)
(<-c) //1
(<-c) //2
(<-c) //0
(<-c) //0
But if read through range, the for loop will jump out after the channel is closed:
c := make(chan int, 10)
c <- 1
c <- 2
close(c)
for i := range c {
(i)
}
Through i, ok := <-c, you can view the status of the Channel and determine whether the value is a zero value or a normal read value.
c := make(chan int, 10)
close(c)
i, ok := <-c
("%d, %t", i, ok) //0, false
synchronous
Channels can be used for synchronization between goroutines.
In the following example, main goroutine waits for the worker to complete the task through the done channel. After the worker completes the task, just send a data to the channel to notify the main goroutine task to complete.
import (
"fmt"
"time"
)
func worker(done chan bool) {
()
// Notification task has been completed
done <- true
}
func main() {
done := make(chan bool, 1)
go worker(done)
// Wait for the task to be completed
<-done
}