preface

In Go, you can not only use atomic functions and mutex to guarantee safe access to shared resources and eliminate contention, but also use channels to synchronize between Goroutines by sending and receiving shared resources.

If a Goroutine is an executor of Go concurrency, then the “channel” is the connection between them.

Introduction to the

A channel is a mechanism for communication between two Goroutines. When a resource needs to be shared between goroutines, channels provide a conduit between goroutines and a mechanism to ensure that data is exchanged synchronously.

Create channels

When creating a channel, you need to specify the type of data to be shared. Values or Pointers of built-in, named, structural, and reference types can be shared through channels.

A channel is a reference type in Go. Use the built-in function make to create a channel in the following format:

= make(chan data type)Copy the code

The first argument to make needs to be the keyword chan, followed by the type of data that the channel is allowed to exchange. If you are creating a buffered channel, then you need to specify the size of the channel’s buffer in the second argument.

  • Unbuffered channels

An unbuffered channel is one that does not have the ability to store any values before receiving them. This type of channel requires both the send and receive goroutine to be ready at the same time to complete the send and receive operations. If both goroutines are not ready at the same time, the channel causes the gotoutine that performed the send or receive operation first to block and wait. This interaction of sending and receiving a channel is itself synchronous. Neither operation can exist in isolation from the other.

// unbuffered := make(chan int)Copy the code
  • There are buffered channels

A buffered channel is a channel that can store one or more values before they are received. This type of channel does not enforce that both send and receive must be completed between Goroutines. The channel will block and the conditions for sending and receiving actions will be different. The receive action blocks only if there are no values to receive in the channel. The send action blocks only if the channel has no available buffer to hold the value being sent.

Chuang buffered := make(chan string, 10)Copy the code

Note: If the channel is not buffered, the sender blocks until the receiver receives a value from the channel. If the channel is buffered, the sender blocks until the sent value is copied into the buffer. If the buffer is full, it means waiting until one of the recipients gets a value. The receiver blocks until there is a value to receive.

Channel by value

The <- operator is used to specify the direction of the channel, send or receive. If no direction is specified, it is a bidirectional channel. The format is as follows:

Channel variable <- valueCopy the code
  • Channel variables: Channel instances created by make.
  • Value: can be a variable, constant, expression, function return value, etc. The type of the value must be the same as the element type of the CH channel.

The following is an example:

// Send the value ch < -v // send the value v to the channel CH // receive the value v from the channel, ok := <-ch // Receive the data from ch and assign the value to v, if the channel can not receive the data ok isfalse
Copy the code

Traverse channel

Go receives channel data by traversing the channel using the range function.

package main
 
import "fmt"
 
func mainQueue := make(chan string, 2) queue <-"one"
    queue <- "two"The close(queue) /* range function iterates over each data received from the channel, since the queue closes the channel after sending two more data, so our range function ends after receiving two more data. If the above queue channel is not closed, the range function will not end and will block on receiving the third data. * /for elem := range queue {
        fmt.Println(elem)
    }
}
Copy the code

The output is as follows:

one
two
Copy the code
  • For and range provide iteration capabilities for basic data structures, as well as for channel traversal
  • The above example iterates through two values in channel Queue
  • We close the channel, so we’re done iterating through the two values. If we don’t close, we’ll block until the third value is received
  • This example shows that a non-empty channel can also be closed, but the remaining values in the channel can still be received

Close the channel

A channel is a reference type. In the absence of any external references, the Go program automatically garbage collects the channel at runtime, and the channel can be actively closed.

Use close() to close a channel format:

close(ch)
Copy the code

1. Sending data to the closed channel triggers panic

Closed channels are not set to nil, and an outage will be triggered if you try to send to a closed channel. The following is an example:

package main
import "fmt"
func main() {make(chan int) ch := make(chan int) close(ch) FMT.Printf()"ptr:%p cap:%d len:%d\n", ch, cap(ch), len(ch)) // Send data to closed channelCopy the code

Outage triggered after code runs:

panic: send on closed channel
Copy the code

2. No blocking occurs when receiving data from a closed channel

When receiving data from or in the process of receiving data from a closed channel, a zero value of the channel type is received, blocking is stopped and returned.

The following is an example:

package main
import "fmt"
func mainMake (chan int, 2) // make(chan int, 2) // make(chan int, 2) // make(chan int, 2) //for i := 0; i < cap(ch)+1; Println(v, ok) {if (v, ok) {if (v, ok)}}Copy the code

The code results are as follows:

0 true
1 true
0 false
Copy the code

The first two lines of the above results correctly output the buffered channel data, indicating that the buffered channel can still access the internal data even after it is closed. The “0 false” in line 3 of the above results represents the value fetched by the channel in the closed state. 0 indicates the default value for this channel, false indicates no success because the channel is empty. We found that after the channel was closed, fetching data would not block even if the channel had no data, but fetching data would fail.

Using channel examples

  • Unbuffered channels

An important function of the unbuffered channel is to synchronize interactive data between the two Goroutines.

In tennis, two players pass the ball back and forth between them. A player is always in one of two states: either waiting to catch the ball, or hitting it at his opponent. Two goroutines can be used to simulate a tennis match, and uncushioned channels can be used to simulate the ball going back and forth. As follows:

// This sample program shows how to simulate a tennis match between two Goroutines using an unbuffered channel."fmt"
	"math/rand"
	"sync"
	"time"Var wg sync.waitgroup funcinit() {rand.seed (time.now ().unixnano ())} // Main is the entry func for all Go programsmain() {make(chan int) court := make(chan int) wg.add (2)"Zhang", court)
	go player("Bill"Wg.wait ()} // Player (name string) func Player (name string) Court chan int) {// Call Done when the function exits to inform main that the work has finished defer wg.done ()for{// wait for the ball to hit, ball, ok := <-courtif! Ok {// If the channel is closed, we win FMT.Printf("Player % S wins \n", name)
			return} // select a random number and use it to determine if we lost the ball n := rand.Intn(100)if n%3 == 0 {
			fmt.Printf("Player % S lost \n", name) // Close (court)return} // Display the number of shots, and add 1 to the number of shots"Player %s hits %d\n", name, ball) ball ++ // Play the ball to the opponent court < -ball}}Copy the code

After execution, the following output is randomly generated:

Player Li Si hits player 1 and Player Zhang SAN hits player 2 and player Li Si loses and player Zhang SAN winsCopy the code
  • There are buffered channels

// This sample program shows how to use buffered channels and a fixed number of goroutines to handle a bunch of work."fmt"
	"math/rand"
	"sync"
	"time") const (numberGoroutines = 4 // Number of goroutines to use taskLoad = 10 // Number of goroutines to work on) // Used by WG to wait for the program to complete var WG sync.waitGroup // init initializes the package. The Go language executes this function func before any other code executesinit() {// initialize the random number Seed rand.seed (time.now ().unix ())} // main is the entry func for all Go programsmainWg.add (numberGoroutines) {make(chan string, taskLoad) // Start goroutine to handle tasks:forgr := 1; gr <= numberGoroutines; Gr ++ {go worker(tasks, gr)} // Add a group of tasks to completefor post := 1; post <= taskLoad; post++ {
		tasks <- fmt.Sprintf("Task: %d"// Close the channel when all the work is done, Wg.wait ()} // The worker starts as a Goroutine to process // the work func worker(tasks) passed in from the buffered channel Chan string, worker int) {// Inform the function that it has deferred wg.done ()for{// Wait to assign work task, ok := <-tasksif! FMT.Printf() {// This means that the channel is finished and closed."Worker %d : Shutting Down\n", worker)
			return} // show that we are working"Worker: %d : Started %s\n", worker, Sleep := rate.int63n (100) time.sleep (time.duration (sleep) * time.millisecond) // indicate that we are finished fmt.Printf("Worker: %d : Completed %s\n", worker, task)

	}

}

Copy the code

After execution, the following output is randomly generated:

Worker: 1 : Started Task: 2
Worker: 3 : Started Task: 1
Worker: 4 : Started Task: 3
Worker: 2 : Started Task: 4
Worker: 4 : Completed Task: 3
Worker: 4 : Started Task: 5
Worker: 3 : Completed Task: 1
Worker: 3 : Started Task: 6
Worker: 4 : Completed Task: 5
Worker: 4 : Started Task: 7
Worker: 1 : Completed Task: 2
Worker: 1 : Started Task: 8
Worker: 2 : Completed Task: 4
Worker: 2 : Started Task: 9
Worker: 2 : Completed Task: 9
Worker: 2 : Started Task: 10
Worker: 3 : Completed Task: 6
Worker 3 : Shutting Down
Worker: 4 : Completed Task: 7
Worker 4 : Shutting Down
Worker: 2 : Completed Task: 10
Worker 2 : Shutting Down
Worker: 1 : Completed Task: 8
Worker 1 : Shutting Down
Copy the code

Because the program and the Go language’s scheduler have a random component, the program will get different output each time it executes. However, using all four Goroutines to do the work through the buffered channel remains the same. You can see from the output how each Goroutine receives work distributed from the channel.