Deep understanding of Golang Channel concurrent programming

1, Basic introduction

Golang implements the CSP(Communicating Sequential Processes) model using groove and channels. Chanles plays an important role in the communication and synchronization of goroutine.

The advantage of this method is that it avoids the complex locking mechanism in race condition by providing atomic communication primitives.
channel can be regarded as a FIFO queue. Reading and writing to the FIFO queue are atomic operations without locking.

The definition format of Channel type in Golang language is as follows:

chan Tr          // Data of type Tr can be received and sent
chan <- float64  // It can only be used to send data of float64 type
<- chan int      // It can only be used to receive data of type int

The optional < - represents the direction of the channel. If no direction is specified, the channel is bidirectional and can receive or send data.
By default, the sender and receiver of the channel will block until the other party is ready to send or receive, which makes the Go language naturally support concurrency without locking or other conditions.
Example:

c := make(chan bool) 	//Create a bufferless bool Channel
c <- x        			//Send a value to a Channel
<- c          			//Receive a value from a Channel
x = <- c      			//Receive a value from Channel c and store it in x
x, ok = <- c  			//Receive a value from the channel. If the channel is closed or has no data, ok will be set to false

Use make to initialize the Channel. You can also set the capacity:

make(chan int, 100)      //Create a buffered int Channel

Introduction to Channel buffering
Capacity represents the maximum number of elements held by the Channel and the buffer size of the Channel. If the capacity is not set, or if the capacity is set to 0, the Channel has no buffer.
In actual development, you can receive/send data from / to one channel in multiple goroutine s without considering additional synchronization measures.
The Channel can be used as a first in first out (FIFO) queue. The order of received data and transmitted data is consistent. The unbuffered Channel has both communication and synchronization characteristics.

2, channel application instance

Example 1 code is as follows

package main

 import "fmt"

 func main() {
         ch1 := make(chan int,2)
         //send
         go func() {
                 for i := 0; i < 8; i++ {
                         fmt.Printf("Sender :sending element %v ..\n",i)
                         ch1 <- i
                 }

                 fmt.Println("Sender : close the channel ...")
                 close(ch1)
         }()

         //receiver
         for {
                 elem, ok := <- ch1
                 if !ok {
                         fmt.Println("Receiver: closed channel..")
                         break
                 }
                 fmt.Printf("Receiver: received an element: %v \n",elem)
         }

         fmt.Println("main end..")
 }

In the program of the above example, we create a buffered INT type channel, then send sequence data to the channel in a goroutine loop, and then read the contents of < - ch1 in a goroutine loop.
Here, we use the channel to realize the communication between threads (to be exact, between goroutine s), because the sender and receiver of the channel will block until the other party is ready to send or receive, which makes us wait for ch1 to close without other synchronization operations.

  • Cache ch1: = make (Chan int, 0) or ch1: = make (Chan int); The operation results are as follows:
~/gomod/src/channel$ go run channel.go 
Sender :sending element 0 ..
Sender :sending element 1 ..
Receiver: received an element: 0 
Receiver: received an element: 1 
Sender :sending element 2 ..
Sender :sending element 3 ..
Receiver: received an element: 2 
Receiver: received an element: 3 
Sender :sending element 4 ..
Sender :sending element 5 ..
Receiver: received an element: 4 
Receiver: received an element: 5 
Sender :sending element 6 ..
Sender :sending element 7 ..
Receiver: received an element: 6 
Receiver: received an element: 7 
Sender : close the channel ...
Receiver: closed channel..
main end..
  • Cache ch1: = make (Chan, int, 5); The operation results are as follows:
robot@ubuntu:~/gomod/src/channel$ go run channel.go 
Sender :sending element 0 ..
Sender :sending element 1 ..
Sender :sending element 2 ..
Sender :sending element 3 ..
Sender :sending element 4 ..
Sender :sending element 5 ..
Sender :sending element 6 ..
Receiver: received an element: 0 
Receiver: received an element: 1 
Receiver: received an element: 2 
Receiver: received an element: 3 
Receiver: received an element: 4 
Receiver: received an element: 5 
Receiver: received an element: 6 
Sender :sending element 7 ..
Sender : close the channel ...
Receiver: received an element: 7 
Receiver: closed channel..
main end..
  • Cache ch1: = make (Chan, int, 10); The operation results are as follows:
robot@ubuntu:~/gomod/src/channel$ go run channel.go 
Sender :sending element 0 ..
Sender :sending element 1 ..
Sender :sending element 2 ..
Sender :sending element 3 ..
Sender :sending element 4 ..
Sender :sending element 5 ..
Sender :sending element 6 ..
Sender :sending element 7 ..
Sender : close the channel ...
Receiver: received an element: 0 
Receiver: received an element: 1 
Receiver: received an element: 2 
Receiver: received an element: 3 
Receiver: received an element: 4 
Receiver: received an element: 5 
Receiver: received an element: 6 
Receiver: received an element: 7 
Receiver: closed channel..
main end..

Example 2 code is as follows:

package main

import (
        "fmt"
        "time"
)

func main() {
        N := 10
        exit := make (chan struct{})
        done := make(chan struct{},N)

        for i :=0; i< N; i++ {
                go func(n int){
                        for {
                                select {
                                case <- exit:
                                        fmt.Printf("worker goroutine # %d exit \n\r ",n)
                                        done <- struct{}{}
                                        return
                                case <- time.After(time.Second):
                                        fmt.Printf("worker goroutine # %d is working ..\n", n)
                                }
                        }
                }(i)
        }

        time.Sleep(3*time.Second)
        fmt.Printf("sender exit channel... \n\r")
        close(exit)  //Close the channel to notify the coroutine to exit the thread

        for i := 0; i < N;i++ {
                <- done
        }

        fmt.Printf("main goroutine exit..\n")
}

The operation effect is as follows:

robot@ubuntu:~/gomod/src/channel$ go run channel1.go 
worker goroutine # 8 is working ..
worker goroutine # 5 is working ..
worker goroutine # 0 is working ..
worker goroutine # 2 is working ..
worker goroutine # 9 is working ..
worker goroutine # 4 is working ..
worker goroutine # 6 is working ..
worker goroutine # 7 is working ..
worker goroutine # 3 is working ..
worker goroutine # 1 is working ..
worker goroutine # 8 is working ..
worker goroutine # 9 is working ..
worker goroutine # 5 is working ..
worker goroutine # 0 is working ..
worker goroutine # 2 is working ..
worker goroutine # 6 is working ..
worker goroutine # 4 is working ..
worker goroutine # 7 is working ..
worker goroutine # 3 is working ..
worker goroutine # 1 is working ..
sender exit channel... 
worker goroutine # 8 exit 
 worker goroutine # 2 exit 
 worker goroutine # 1 exit 
 worker goroutine # 0 exit 
 worker goroutine # 5 exit 
 worker goroutine # 9 exit 
 worker goroutine # 6 exit 
 worker goroutine # 7 exit 
 worker goroutine # 3 exit 
 worker goroutine # 4 exit 
 main goroutine exit..

After closing the exit pipeline, each goroutine Ctrip detects that the exit pipeline is closed and automatically exits the process.

Added by Cooper94 on Fri, 31 Dec 2021 21:37:09 +0200