Channel is a core type in Go. You can think of it as a pipe through which concurrent core units can send or receive data for communication.
Its operator is an arrow<- 。
ch <- v // 发送值v到Channel ch中 v := <-ch // 从Channel ch中接收数据,并将数据赋值给v
(The direction of the arrow indicates the flow of data)
Just like the map and slice data types, a channel must be created before it can be used:
ch := make(chan int)
Channel Type
The format for defining a Channel type is as follows:
ChannelType = ( "chan" | "chan" "<-" | "<-" "chan" ) ElementType .
It includes three types of definitions. The optional<-represents the direction of the channel. If no direction is specified, the Channel is bidirectional, capable of both receiving and sending data.
chan T // 可以接收和发送类型为 T 的数据 chan<- float64 // 只可以用来发送 float64 类型的数据 <-chan int // 只可以用来接收 int 类型的数据
<-It always binds to the leftmost type first.
chan<- chan int // 等价 chan<- (chan int) chan<- <-chan int // 等价 chan<- (<-chan int) <-chan <-chan int // 等价 <-chan (<-chan int) chan (<-chan int)
UsagemakeInitialize a Channel, and you can set its capacity:
make(chan int, 100)
The capacity represents the maximum number of elements the Channel can hold, indicating the size of the Channel's buffer.
If no capacity is set, or the capacity is set to 0, it means the Channel has no buffer, and communication will only occur (Blocking) when both the sender and receiver are ready. If a buffer is set, blocking may not occur: send blocks only after the buffer is full, and receive blocks only after the buffer is empty. A nil channel never communicates.
The built-inclosemethod can close the Channel.
You can have multiple goroutines receive/send data from/to a channel without needing extra synchronization measures.
A Channel can act as a first-in, first-out (FIFO) queue, where the order of received data is consistent with the order of sent data.
The channel's receive supportsmulti-valued assignment, such as
v, ok := <-ch
It can be used to check whether the Channel has been closed.
- send statement
The send statement is used to send data to a Channel, e.g.ch <- 3。
Its definition is as follows:
SendStmt = Channel "<-" Expression . Channel = Expression .
Before communication begins, the channel and expression must be evaluated first. For example, in the following, (3+4) is evaluated to 7 first, then sent to the channel.
c := make(chan int)
defer close(c)
go func() { c <- 3 + 4 }()
i := <-c
fmt.Println(i)
Before the send can proceed, communication is blocked. As mentioned earlier, for an unbuffered channel, send is executed only after the receiver is ready. If there is a buffer and it is not full, the send will be executed.
Sending data to a channel that has already been closed will causerun-time panic。
Sending data to a nil channel will always be blocked.
- receive operator
<-chIt is used to receive data from channel ch. This expression will be blocked until there is data to receive.
Receiving data from a nil channel will always be blocked.
Receiving data from a closed channel is not blocked; it returns immediately. After all sent data has been received, it returns the zero value of the element type.
As mentioned earlier, you can use an additional return parameter to check whether the channel is closed.
x, ok := <-ch x, ok = <-ch var x, ok = <-ch
If OK is false, it indicates that the received x is a generated zero value, and this channel has been closed or is empty.
blocking
By default, sending and receiving remain blocked until the other side is ready. This method can be used for synchronization between goroutines without the need for explicit locks or condition variables.
As in the official example,x, y := <-c, <-cThis line will block until the calculation result is 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
fmt.Println(x, y, x+y)
}Buffered Channels
The second parameter of make specifies the buffer size:ch := make(chan int, 100)。
Through the use of buffering, blocking can be avoided as much as possible, improving application performance.
Range
for …… rangeThe range statement can process Channels.
func main() {
go func() {
time.Sleep(1 * time.Hour)
}()
c := make(chan int)
go func() {
for i := 0; i < 10; i = i + 1 {
c <- i
}
close(c)
}()
for i := range c {
fmt.Println(i)
}
fmt.Println("Finished")
}
range cThe generated iteration values are the values sent in the Channel. It will keep iterating until the channel is closed. In the example above, if youclose(c)comment it out, the program will block forever onfor …… rangethat line.
select
selectThe select statement chooses from a set of possible send and receive operations to handle. It is similar toswitch, but it is only used to handle communication operations.
Itscasecan be a send statement, or a receive statement, or adefault。
receivestatement can assign a value to one or two variables. It must be a receive operation.
At most onedefault case, it can be placed anywhere in the case list, although we 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:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}
If there are multiple cases to process at the same time, for example, multiple channels can receive data, then Go will pseudo-randomly select a case to process. If no case needs processing, it will selectdefaultto process, ifdefault caseexists. If there is nodefault case, thenselectthe statement will block until some case needs processing.
It should be noted that operations on nil channels are always blocked. If there is no default case, a select with only nil channels will be blocked forever.
selectThe select statement and the switchswitchstatement are alike; it is not a loop, it only selects one case to handle. If you want to keep processing channels, you can add an infinite for loop around it:
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
timeout
selectA very important application is timeout handling. Because as mentioned above, if no case needs processing, the select statement will block forever. At this time, we may need a timeout operation to handle timeout situations.
In the following example, we send a piece of data to channel c1 after 2 seconds, butselectset to a 1-second timeout, so we will printtimeout 1, instead ofresult 1。
import "time"
import "fmt"
func main() {
c1 := make(chan string, 1)
go func() {
time.Sleep(time.Second * 2)
c1 <- "result 1"
}()
select {
case res := <-c1:
fmt.Println(res)
case <-time.After(time.Second * 1):
fmt.Println("timeout 1")
}
}
Actually, it uses thetime.Aftermethod, which returns a channel of type<-chan Timeof unidirectional channel, which sends the current time to the returned channel at the specified time.
Timer and Ticker
Let's look at two Channels related to time.
A Timer is a timer that represents a single future event. You can tell the timer how long to wait; it provides a Channel, and at that future time the Channel provides a time value. In the example below, the second line will block for about 2 seconds, and it will not continue until the time arrives.
timer1 := time.NewTimer(time.Second * 2)
<-timer1.C
fmt.Println("Timer 1 expired")
Of course, if you just want to simply wait, you can usetime.Sleepto achieve it.
You can also usetimer.Stopto stop the timer.
timer2 := time.NewTimer(time.Second)
go func() {
<-timer2.C
fmt.Println("Timer 2 expired")
}()
stop2 := timer2.Stop()
if stop2 {
fmt.Println("Timer 2 stopped")
}
tickerA Ticker is a timer that fires periodically. It sends an event (the current time) to the Channel at an interval, and the receiver of the Channel can read events from the Channel at fixed intervals. In the example below, the ticker fires every 500 milliseconds; you can observe the output times.
ticker := time.NewTicker(time.Millisecond * 500)
go func() {
for t := range ticker.C {
fmt.Println("Tick at", t)
}
}()
Similar to a timer, a ticker can also use theStopmethod to stop. Once it stops, receivers will no longer receive data from the channel.
close
The built-in close method can be used to close a channel.
Let's summarize the operations of senders and receivers after a channel is closed.
If channel c has been closed, continuing to send data to it will causepanic: send on closed channel:
import "time"
func main() {
go func() {
time.Sleep(time.Hour)
}()
c := make(chan int, 10)
c <- 1
c <- 2
close(c)
c <- 3
}
But from this closed channel, you can not only read the data that has already been sent, but also continuously read zero values:
c := make(chan int, 10) c <- 1 c <- 2 close(c) fmt.Println(<-c) //1 fmt.Println(<-c) //2 fmt.Println(<-c) //0 fmt.Println(<-c) //0
But if usingrangeto read, the for loop will break out after the channel is closed:
c := make(chan int, 10)
c <- 1
c <- 2
close(c)
for i := range c {
fmt.Println(i)
}
By usingi, ok := <-cyou can check the status of the Channel to determine whether the value is a zero value or a normally read value.
c := make(chan int, 10)
close(c)
i, ok := <-c
fmt.Printf("%d, %t", i, ok) //0, false
Synchronization
Channels can be used for synchronization between goroutines.
In the following example, the main goroutine waits for the worker to complete its task through the done channel. After the worker finishes the task, it only needs to send a piece of data to the channel to notify the main goroutine that the task is complete.
import (
"fmt"
"time"
)
func worker(done chan bool) {
time.Sleep(time.Second)
// 通知任务已完成
done <- true
}
func main() {
done := make(chan bool, 1)
go worker(done)
// 等待任务完成
<-done
}
Source: http://colobu.com/2016/04/14/Golang-Channels/