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.

  1. A channel can be passed as an argument in a function, and when passed as an argument, the reference is copied.
  2. Channels are concurrency safe.
  3. 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.
  4. 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.
  5. If the element is of reference type copied into the channel, then the address of the reference type is copied.
  6. 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.
  7. Unbuffered channels, both read and write, are clogged and require matching operations to execute.
  8. 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.