This article has participated in the Denver Nuggets Creators Camp 3 “More Productive writing” track, see details: Digg project | creators Camp 3 ongoing, “write” personal impact.

Select is a mechanism used in the GO language to provide IO reuse. It can detect whether multiple Chan’s are ready (read/write).

As usual, let’s test the waters with a few questions.

The answer link

  1. What does the following program output?
package main

import (
	"fmt"
	"time"
)

func main(a) {
	chan1 := make(chan int)
	chan2 := make(chan int)
	
	go func(a) {
		chan1 <- 1
		time.Sleep(5 * time.Second)
	}()
	go func(a) {
		chan2 <- 1
		time.Sleep(5 * time.Second)
	}()
	
	select {
		case <- chan1:
			fmt.Println("chan1")
		case <- chan2:
			fmt.Println("chan2")
		default:
			fmt.Println("default")
	}
	fmt.Println("main exit")}Copy the code

The answer: The order of case execution in select is random. If a channel in a case is ready, the corresponding statement is executed and the select process exits. If channels in all cases are not ready, The statement in default is executed and the select process exits.

Since the starting coroutine and the SELECT statement do not guarantee the order of execution, it is also possible that the coroutine has not written data to the channel when the select is executed, so the select directly executes the default statement and exits. Thus, it is possible for the subprogram to produce three outputs:

chan1
main exit
Copy the code
chan2
main exit
Copy the code
default
main exit
Copy the code
  1. What does the following program output?
package main

import (
	"fmt"
	"time"
)

func main(a) {
	chan1 := make(chan int)
	chan2 := make(chan int)
	
	writeFlag := false
	go func(a) {
		for {
			if writeFlag {
				chan1 <- 1
			}
			time.Sleep(5 * time.Second)
		}
	}()
	go func(a) {
		for {
			if writeFlag {
				chan2 <- 1
			}
			time.Sleep(5 * time.Second)
		}
	}()
	
	select {
		case <- chan1:
			fmt.Println("chan1")
		case <- chan2:
			fmt.Println("chan2")
	}
	fmt.Println("main exit.")}Copy the code

The answer: If a channel is ready in any case, execute the corresponding case statement and exit the select process. If all channels are not ready and do not have default, blocking waits for each channel. So the program will always block.

  1. What does the following program output?
package main import ( "fmt" ) func main() { chan1 := make(chan int) chan2 := make(chan int) go func() { close(chan1) }()  go func() { close(chan2) }() select { case <- chan1: fmt.Println("chan1") case <- chan2: fmt.Println("chan2") } fmt.Println("main exit.") }Copy the code

Select will randomly check whether a channel in each case statement is ready. Note that closed channels are also readable, so select does not block.

  1. What does the following program output?
package main

func main(a) {
  select{}}Copy the code

For empty select statements, the program will block, specifically, the current coroutine will block, and Go has a deadlock detection mechanism, when the current coroutine has no chance to wake up, panic will occur. So the above program will panic.

Realize the principle of

When Go implements SELECT, it defines a data structure to represent each case statement (including defaut). The select execution process can be analogous to a function that inputs an array of cases, outputs the selected cases, and then flows to the selected case block.

SRC /runtime/select.go defines the data structure for the case statement:

// Select case descriptor.
// Known to compiler.
// Changes here must also be made in src/cmd/internal/gc/select.go's scasetype.
type scase struct {
	c    *hchan         // chan
	elem unsafe.Pointer // data element
}
Copy the code
  • C: Represents the channel pointer operated by the current case statement
  • Elem: buffer address

generalizations

  • The select statement operates on one channel per case except default, either read or write
  • In the SELECT statement, all cases except default are executed in random order
  • A select statement without a default statement blocks and waits for any case
  • In the SELECT statement, the read operation determines whether the read is successful. A closed channel can also be read