Hi, I’m Xingzhou, today we are learning Channel of Go language.
The Go language adopts the CSP model, allowing two independently executing programs to share memory through message passing, and Channel is the data type Golang uses to complete message communication.
In Go, it is still possible to use shared memory to share data between multiple coroutines, although it is not recommended.
Declare the Channel
Declare a channel
Var Channel type = chan element typeCopy the code
In addition to the above declaration, a <- symbol can be added to the left and right sides of chan to indicate read-only and write-only channels, respectively. Look at a few practical examples:
package main
import "fmt"
func main(a) {
var c1 chan int // Read-write channels
var c2 chan<- float64 // Write only channels
var c3 <-chan int // Read-only channel
fmt.Printf("c1=%+v \n",c1)
fmt.Printf("c2=%+v \n",c2)
fmt.Printf("c3=%+v \n",c3)
}
Copy the code
Only uninitialized channels are declared nil. Storage is allocated only after initialization. Channel initialization uses the make method. The second argument to the make method defines how many arguments the channel can buffer.
c1 := make(chan int) // Initialize the unbuffered channel
c2 := make(chan float64.10) // Initialize a channel that can buffer 10 elements
fmt.Printf("c1=%+v \n",c1)
fmt.Printf("c2=%+v \n",c2)
Copy the code
Channels with no buffer space, such as c1 above, are called unbuffered channels; C2 is called the buffered channel.
Basic usage
Write and read data
We execute the following code
c1 := make(chan int , 10) // Initialize a channel that can buffer 10 elements
c1 <- 1
c1 <2 -
Copy the code
Initialize channel C1 and write data.
Let’s do another example
c1 := make(chan int , 10) // Initialize a channel that can buffer 10 elements
c2 := make(chan float64) // Initialize the unbuffered channel
c1 <- 1 // Write to channel C1
c2 <- 1.01 // Write values to channel C2, an error is reported
Copy the code
You will see an error like this:
fatal error: all goroutines are asleep - deadlock!
Copy the code
This is because we’re writing to c2, which is an unbuffered channel, and C2 is not reading. Let’s add a line of reads to C2
c2 := make(chan float64) // Initialize the unbuffered channel
c1 <- 1 // Write to channel C1
c2 <- 1.01 // Write to channel C2
<-c2
Copy the code
The same error is reported as above. This is because the reads and writes of unbuffered channels must be in different coroutines.
c1 := make(chan int , 10) // Initialize a channel that can buffer 10 elements
c2 := make(chan float64) // Initialize the unbuffered channel
go func(a) {
fmt.Printf("c2=%+v \n", <-c2) // read data in c2, output c2=1.01
}()
c2 <- 1.01 // Write to channel C2
c1 <- 1 // Write to channel C1
time.Sleep(1*time.Second) // Short sleep, waiting for the coroutine to read the channel data
Copy the code
This is the right way to write it, and it will work.
When reading data from a channel, a variable to the left of the channel returns the element in the channel. If there are two variables, the first is the copied element in the channel, and the second is the state of the channel. When the status of the channel is true, the channel is not closed; when the status is FasLE, the channel is closed. Closed channels are not allowed to send data.
c1 := make(chan int , 10) // Initialize a channel that can buffer 10 elements
c1 <- 1 // Write to channel C1
ret,status := <- c1
fmt.Printf("r=%+v,status=%+v",ret,status) / / output r = 1, the status = true
Copy the code
Close the channel
The method to close a channel is the close method.
C := make(chan int,5) c<-1 close(c) c<-2Copy the code
Calling the close method to close channel C and then continuing to send data to channel C raises an error.
panic: send on closed channel
Copy the code
When the close method is called to close a channel, messages are sent to all coroutines waiting to read the channel data. This is a very useful feature.
c := make(chan int)
go func(a) {
ret,status := <-c
fmt.Printf("go rountine 1 ret=%+v,status=%+v \n",ret,status)
}()
go func(a) {
ret,status := <-c
fmt.Printf("go rountine 2 ret=%+v,status=%+v \n",ret,status)
}()
close(c)
time.Sleep(1*time.Second)
Copy the code
Although a channel can be closed, it is not a mandatory method because the channel itself is recycled through the garbage collector based on whether it is accessible or not.
Traverse channel
Traverse all data in the channel
c := make(chan int.5)
c <- 1
c <- 2
c <- 3
c <- 4
c <- 5
go func(a) {
for ret := range c{
fmt.Printf("ret=%d \n",ret)
}
}()
time.Sleep(2*time.Second)
Copy the code
Sleep(1* time.second) is added to the end of the example to make the main program wait for 1s before exiting. Since main is also a Goroutine, it exits as soon as it completes execution, without determining whether any other coroutines need to be executed. We let main Goroutine wait 1s to give the other coroutines enough time to execute.
select
Select is a control structure in Golang, similar to switch statements written in other languages. However, the SELECT case statement must be a channel read/write operation.
If more than one case can be run, one case is selected at random and the other cases are not executed. Default can always be executed when no case is available.
The following example
c1 := make(chan int ,10)
c2 := make(chan int ,10)
c3 := make(chan int ,10)
var i1, i2 int
c1 <- 10
c3 <- 20
select {
case i1 = <-c1:
fmt.Printf("received i1=%d \n", i1) // Received i1=10
case c2 <- i2:
fmt.Printf("sent %d \n", i2 ) // Output sent 0
case i3, ok := (<-c3): // equivalent to i3, ok := <-c3
if ok {
fmt.Printf("received i3=%d \n", i3) // Received i3=20
} else {
fmt.Printf("c3 is closed\n")}default:
fmt.Printf("no communication\n")}Copy the code
If we run this code many times, we will see that all three cases are likely to be executed. This also verifies that when a select operation satisfies multiple cases, it will randomly select one of those cases to execute.
If no case condition is met in the SELECT statement and no default statement is defined, the current select coroutine will be blocked.
With time.after (1* time.second), the method sends a message to the channel After 1s to complete the timeout operation on the SELECT:
c1 := make(chan int)
select{
case <- c1:
fmt.Println("print c1" )
case <-time.After( 1* time.Second):
fmt.Println("Print 1 s clock")}Copy the code
Select is often used with for, and here are some examples of both being used together:
c1 := make(chan int ,10)
// Put the elements of the array into a Channel
for _, str := rang []string{"a"."b"."c"} {
select{
case <- done:
return
case c1 <- str
}
}
Copy the code
Done := make(chan int) for{select{case < -done: return default: // do something}}Copy the code
Channel characteristics
From studying the Golang language source code and some tutorials, channels have several important features that you need to understand and keep in mind.
- A channel can be passed as an argument in a function, and when passed as an argument, the reference is copied.
- Channels are concurrency safe.
- The send operations on the same channel are mutually exclusive and must be executed before the next one is executed. The receive operation is the same as the send operation.
- The send operation of the buffered channel requires copying the element value and then storing a copy in the channel. A non-buffered channel copies a copy of the element value directly to the receive operation.
- If the element is of reference type copied into the channel, then the address of the reference type is copied.
- Sending data to the buffer channel after the buffer channel is full will block. When a value is removed, the earliest blocked Goroutine is notified to resend the data. If the value in the buffer channel is empty, receiving data from the buffer channel will also be blocked, and when a new value arrives, the goroutine that was blocked first will be notified to receive it again.
- Unbuffered channels, both read and write, are clogged and require matching operations to execute.
- For a newly initialized nil channel, its send and receive operations block forever.
Advanced examples
We use channels to complete two common questions to deepen our understanding of channels. First, by means of a channel, two coroutines alternately output upper and lower case letters.
package main
import (
"fmt"
"time"
)
func main(a) {
arr1 := []string{"a"."b"."c"."d"."e"}
arr2 := []string{"A"."B"."C"."D"."E"}
a := make(chan bool)
b := make(chan bool)
go func(a) {
for _,str := range arr1{
if <-a {
fmt.Printf(str)
b <- true}}} ()go func(a) {
for _,str := range arr2{
if <-b {
fmt.Printf(str)
a <- true
}
}
}()
a<-true
time.Sleep(2*time.Second)
}
Copy the code
We define two channels, A and B, which take advantage of the unbuffered channel receiving blockage. In the two Goroutines, the value of the channel is received and used as the basis to continue execution, so as to achieve the purpose of alternate execution.
Second, climb the specified web site
package main
import (
"fmt"
"time"
)
// Grab web content
func crawl(url string) (result string) {
time.Sleep(1*time.Second) // Sleep for 1s to simulate capturing data
return url+": Grab content done \n"
}
// Save the contents of the file locally
func saveFile(url string,limiter chan bool, exit chan bool) {
fmt.Printf("Open a grasping coroutine \n")
result := crawl(url) // Grab web content
ifresult ! ="" {
fmt.Printf(result)
}
<-limiter // Notify the speed limiting coroutine that fetching is complete
if(exit ! =nil){
exit<-true // Notify exit coroutine, program execution complete}}// urls are the address to crawl, n concurrent goroutine restrictions
func doWork(urls []string,n int) {
limiter := make(chan bool,n) // Speed limiting coroutine
exit := make(chan bool) // Exit coroutine
for i,value := range urls{
limiter <- true
if i == len(urls)- 1 {
go saveFile(value,limiter,exit)
}else{
go saveFile(value,limiter,nil)
}
}
<-exit
}
func main(a) {
urls := []string{"https://www.lixiang.com/"."https://www.so.com"."https://www.baidu.com/"."https://www.360.com/"}
doWork(urls, 1)}Copy the code
We control the concurrency of the limiter coroutine by the size of the buffer. The final program is terminated by blocking the exit coroutine.
Realize the principle of
Channel is represented by the hchan structure in Golang.
type hchan struct {
qcount uint // channel Indicates the number of elements in the channel
dataqsiz uint // The size of the data in the ring queue
buf unsafe.Pointer // The location of the actual element
elemsize uint16 // Size of the channnel type
closed uint32 // Whether channnel is closed
elemtype *_type // The type of the element in channel
sendx uint // The position of the sent goroutine in the BUF
recvx uint // The position of the received goroutine in the BUF
recvq waitq // The goroutine queue waiting to be read
sendq waitq // The goroutine queue waiting to be written
// lock protects all fields in hchan, as well as several
// fields in sudogs blocked on this channel.
//
// Do not change another G's status while holding this lock
// (in particular, do not ready a G), as this can deadlock
// with stack shrinking.
lock mutex // channel concurrency lock
}
Copy the code
Buf stores all the buffered data. Combined with Sendx and RECvx, a ring queue structure is constructed.
When a channel is initialized, the allocation of storage space depends on the size of the element and whether it contains a pointer. When the element size is 0, only the memory of the Hchan structure is allocated. When there are no Pointers, memory for both the element size and the structure size is allocated consecutively. When Pointers are present, the pointer elements need to be allocated separate memory space.
When a channel writes data, it first checks whether there is a coroutine waiting to be read. If so, it copies data to this coroutine. Otherwise, continue to determine whether there is a free buffer, if so, copy the data to the buffer; Otherwise, the current goroutine is put into the write queue.
The process of channel data reading is similar to that of writing. First, determine whether there is a coroutine waiting to be written. If so, start the writing operation of the coroutine and copy the data. Otherwise, continue to determine whether there is data in the buffer and copy the data if there is; Otherwise, the current Goroutine is placed in a queue waiting to be read
Go Channel source, mainly in the Runtime /chan. Go directory.
conclusion
This paper mainly introduces the basic usage, characteristics, common scenarios and implementation principles of Go Channel.
We sincerely invite you to pay attention to the public account: Nhat hanzhou I will update technical articles every week, and learn and progress together with you.