keywords

Channel, blocking, panic, select

Introduction:

Channels are a feature of the Golang language and are used for communication between multiple Goroutines. This article first explores the characteristics of the send and receive operations, then discusses when a channel blocks, when a channel panic, and finally, the select statement for a channel

Channel Indicates the characteristics of channel sending and receiving operations

  • The send and receive operations are mutually exclusive

Explanation: When multiple Goroutines send or receive messages to the same channel at the same time, only one Goroutine can send or receive messages to a channel. The messages are mutually exclusive, and the messages are mutually exclusive.

  • The send operation is blocked until it is complete, as is the receive operation.
  • The processing of element values in both send and receive operations is indivisible.

Explanation: Instead of putting a value directly into a channel, the first step is to copy the value, and the second step is to add the copy to the channel. This process is indivisible and blocks. Instead of assigning a value directly to a variable, the first step is to copy, the second step is to assign a copy to the variable, and the third step is to delete the corresponding element in the channel. The process is indivisible and blocks.

When do send and receive operations on channels block?

  • Channel fornilBoth the send and receive operations block

Description: Channel is a reference type, only declared without make initialization, channel is nil

package main

import (
	"fmt"
	"time"
)

func main(a)  {
	var ch chan int
	go func(a) {
		ch <- 1
		fmt.Println("goroutine finish")
	}()
	time.Sleep(10*time.Second)
}
// The main goroutine waits for 10 seconds. Goroutine finish is not printed, indicating that the block has been blocked
Copy the code
  • Buffer channel, full, send operation, send operation blocked
package main

import (
	"fmt"
	"time"
)

func main(a) {
	var ch = make(chan int.3)
	ch <- 1
	ch <- 2
	ch <- 3
	fmt.Println("channel is fill")
	go func(a) {
		ch <- 4
		fmt.Println("fill channel can receive a number?")
	}()
	time.Sleep(5 * time.Second)
}
Copy the code
  • Buffer channel, empty, receive operation, receive operation blocked
  • Non-buffered channels do not block until both send and receive are ready (in effect, synchronized).

package main

func main(a) {
	// Example 1.
	ch1 := make(chan int.1)
	ch1 <- 1
	//ch1 < -2 // The channel is full, so this is blocking.

	// Example 2.
	ch2 := make(chan int.1)
	//elem, ok := <-ch2 // The channel is empty, so there will be a block.
	//_, _ = elem, ok
	ch2 <- 1

	// Example 3.
	var ch3 chan int
	//ch3 < -1 // the value of the channel is nil, so this will cause permanent blocking!
	//<-ch3 // The value of the channel is nil, so this will cause permanent blocking!
	_ = ch3
}
Copy the code

When are channel receive and send raisedpanic?

Conclusion: It is related to the closure of channel

  • The channel is closed, but elements are still being sent to it
package main

func main(a)  {
	var ch = make(chan int.2)
	close(ch)
	ch <- 1
}
Copy the code
  • Close the closed channel again
package main

func main(a)  {
	var ch = make(chan int.2)
	close(ch)
	//ch <- 1
	close(ch)
}
Copy the code

An example of a normal sending and receiving channel

package main

import "fmt"

func main(a) {
	var ch = make(chan int.2)

	go func(a) {
		for i := 0; i < 10; i++ {
			fmt.Println("sender channel a val: ", i)
			ch <- i
		}
		fmt.Println("sender finished, will close the channel")
		close(ch)
	}()

	for {
		elem, ok := <- ch
		if! ok { fmt.Println("channel has already closed.")
			break
		} else {
			fmt.Println("receive a val from channel, ", elem)
		}
	}
	fmt.Println("End.")}Copy the code

select

Basic concept

The SELECT statement is designed specifically for the channel, so each case expression of a SELECT can contain only expressions that operate on the channel, such as receive and send expressions

Select is essentially a listener for an I/O operation on a channel.

// If a channel is not empty, the corresponding statement is executed

// Prepare several channels.
intChannels := [3]chan int{
  make(chan int.1),
  make(chan int.1),
  make(chan int.1),}// Select a channel at random and send element values to it.
index := rand.Intn(3)
fmt.Printf("The index: %d\n", index)
intChannels[index] <- index
// Whichever channel has a desirable element value, the corresponding branch will be executed.
select {
case <-intChannels[0]:
  fmt.Println("The first candidate case is selected.")
case <-intChannels[1]:
  fmt.Println("The second candidate case is selected.")
case elem := <-intChannels[2]:
  fmt.Printf("The third candidate case is selected, the element is %d.\n", elem)
default:
  fmt.Println("No candidate case is selected!")}Copy the code

Pay attention to the point

  • selectthecaseThe branch must be a send or receive expression for a channel.
  • ifselectWith the defaultdefaultBranch, then whatevercaseWhether the operation on the channel in the condition blocks,selectStatements do not block.
  • selectThere can be only one statementdefaultBranch, and the location of the branch doesn’t matter. Are allevaluatedThe variouscaseThe branch is executed after and if it is not satisfieddefaultBranch statements.
  • ifselectThere is no defaultdefaultBranch, andcaseConditions block operations on channels,selectThe statement will block. If blocking occurs, the case expression does not meet the execution criteria and proceeds to the nextcaseBranching judgment.
  • aselectStatements can only be applied to each of thesecaseEach expression is evaluated once, and if you want to evaluate it iteratively, you need toforStatement help. But notice if at this pointselectIn thecaseThe statement hasbreakExecute, then it will not jump out of the outer layerforStatement, but will continue the loop.
  • ifselectStatement discovery has more than one at a timecaseThe branch meets the selection criteria, and then it uses a pseudo-random algorithm to choose one of those branches to execute. Notice, even thoughselectThis is also done in cases where statements are discovered when awakened.
func example2(a) {
	intChan := make(chan int.1)
	// Close the channel after one second.
	time.AfterFunc(time.Second, func(a) {
		close(intChan)
	})
	select {
	case _, ok := <-intChan:
		if! ok { fmt.Println("The candidate case is closed.")
			break
		}
		fmt.Println("The candidate case is selected.")}}// result
The candidate case is closed.
Copy the code

Select * from open; select * from open; select * from open; select * from open; select * from open;

Look again at an example of which case is executed, or which default is executed

package main

import "fmt"

var channels = [3]chan int{
	nil.make(chan int),
	nil,}var numbers = []int{0.1.2}

func main(a) {
	select {
	case getChan(0) <- getNumber(0):
		fmt.Println("The first candidate case is selected.")
	case getChan(1) <- getNumber(1):
		fmt.Println("The second candidate case is selected.")
	case getChan(2) <- getNumber(2):
		fmt.Println("The third candidate case is selected")
	default:
		fmt.Println("No candidate case is selected!")}}func getNumber(i int) int {
	fmt.Printf("numbers[%d]\n", i)
	return numbers[i]
}

func getChan(i int) chan int {
	fmt.Printf("channels[%d]\n", i)
	return channels[i]
}

// result
No candidate case is selected!
Copy the code

Why? Because all three case branches block first and third: trying to send data to the nil channel, blocking the second trying to send data to the unbuffered channel, because there is no corresponding receive operation, the blocking can only execute the statement of the default branch

If you want the second case branch to run, you just need to make the following changes

package main

import "fmt"

var channels = [3]chan int{
	nil.make(chan int),
	nil,}var numbers = []int{0.1.2}

func main(a) {
	go func(a) {
		for {
			<- getChan(1)}} ()select {
	case getChan(0) <- getNumber(0):
		fmt.Println("The first candidate case is selected.")
	case getChan(1) <- getNumber(1):
		fmt.Println("The second candidate case is selected.")
	case getChan(2) <- getNumber(2):
		fmt.Println("The third candidate case is selected")
	default:
		fmt.Println("No candidate case is selected!")
	}
	fmt.Println("finish")}func getNumber(i int) int {
	fmt.Printf("numbers[%d]\n", i)
	return numbers[i]
}

func getChan(i int) chan int {
	fmt.Printf("channels[%d]\n", i)
	return channels[i]
}
Copy the code

A Goroutine receives elements of an unbuffered channel

The resources

1. Geek Time: Core 36 Lectures of Go language