This is the 23rd article in the “Learn to Go” series

Select the role of

Select is used somewhat like a switch statement, except that select has no input value and is only used for channel operations. Select is used to select from multiple send or receive channel operations. The statement blocks until there are channels to operate on, and if there are more than one channel to operate on, one case is randomly selected for execution. Here’s an example:

func service1(ch chan string) {
	time.Sleep(2 * time.Second)
	ch <- "from service1"
}
func service2(ch chan string) {
	time.Sleep(1 * time.Second)
	ch <- "from service2"
}

func main(a) {
	ch1 := make(chan string)
	ch2 := make(chan string)
	go service1(ch1)
	go service2(ch2)
	
	select {       // Will send block
	case s1 := <-ch1:
		fmt.Println(s1)
	case s2 := <-ch2:
		fmt.Println(s2)
	}
}
Copy the code

Output: from Service2 The above example blocks when executing the SELECT statement. The main coroutine waits for a case operation to execute. It is clear that Service2 is ready to read the data (sleep for 1s), so output from service2. Take a look at the situation when both operations are ready:

func service1(ch chan string) {
	//time.Sleep(2 * time.Second)
	ch <- "from service1"
}
func service2(ch chan string) {
	//time.Sleep(1 * time.Second)
	ch <- "from service2"
}

func main(a) {
	ch1 := make(chan string)
	ch2 := make(chan string)
	go service1(ch1)
	go service2(ch2)

	time.Sleep(2*time.Second)
	select {
	case s1 := <-ch1:
		fmt.Println(s1)
	case s2 := <-ch2:
		fmt.Println(s2)
	}
}
Copy the code

We have commented out the delay in the function. The main function select is followed by a delay of 2s to wait for the data of the two channels to be ready. The select will randomly select one case to execute, so the output is also random.

default case

Like the switch statement, the SELECT statement has a default case. Yes, the SELECT statement is not blocked. If other channel operations are not ready, the default branch will be executed directly.

func service1(ch chan string) {
	ch <- "from service1"
}
func service2(ch chan string) {
	ch <- "from service2"
}

func main(a) {
	ch1 := make(chan string)
	ch2 := make(chan string)
	go service1(ch1)
	go service2(ch2)

	select {         // ch1 ch2 are not ready yet, execute the default branch directly
	case s1 := <-ch1:
		fmt.Println(s1)
	case s2 := <-ch2:
		fmt.Println(s2)
	default:
		fmt.Println("no case ok")}}Copy the code

No case OK Execute default statement because channel CH1 and CH2 are not ready.

func service1(ch chan string) {
	ch <- "from service1"
}
func service2(ch chan string) {
	ch <- "from service2"
}

func main(a) {
	ch1 := make(chan string)
	ch2 := make(chan string)
	go service1(ch1)
	go service2(ch2)

	time.Sleep(time.Second)   // Delay for 1s until CH1 and CH2 are ready
	
	select {
	case s1 := <-ch1:
		fmt.Println(s1)
	case s2 := <-ch2:
		fmt.Println(s2)
	default:
		fmt.Println("no case ok")}}Copy the code

A 1s delay was added before the SELECT statement to wait for CH1 and CH2 to be ready. Since both channels are ready, the default statement will not be used. Randomly output from service1 or from service2.

nil channel

The default value of a channel is nil. You cannot read or write to a nil channel. Look at the following example

func service1(ch chan string) {
	ch <- "from service1"
}

func main(a) {

	var ch chan string
	go service1(ch)
	select {
	case str := <-ch:
		fmt.Println(str)
	}
}
Copy the code

Error:

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [select (no cases)]:

goroutine 18 [chan send (nil chan)]:
Copy the code

There are two errors to note: [SELECT (no cases)] The case branch is ignored if the channel is nil, then the empty SELECT {} statement blocks the main coroutine, schedules the service1 coroutine, operates on the nil channel, [chan send (nil chan)] error is reported. You can avoid this error by using the default case above.

func service1(ch chan string) {
	ch <- "from service1"
}

func main(a) {

	var ch chan string
	go service1(ch)
	select {
	case str := <-ch:
		fmt.Println(str)
	default:
		fmt.Println("I am default")}}Copy the code

Output: I am default

Adding a timeout

Sometimes, we do not want to execute the default statement immediately. Instead, we want to wait for a period of time, and execute the specified statement if there are no operational channels within that period. You can set the timeout after the case statement.

func service1(ch chan string) {
	time.Sleep(5 * time.Second)
	ch <- "from service1"
}
func service2(ch chan string) {
	time.Sleep(3 * time.Second)
	ch <- "from service2"
}

func main(a) {
	ch1 := make(chan string)
	ch2 := make(chan string)
	go service1(ch1)
	go service2(ch2)

	select {       // Will send block
	case s1 := <-ch1:
		fmt.Println(s1)
	case s2 := <-ch2:
		fmt.Println(s2)
	case <-time.After(2*time.Second):     / / wait for 2 s
		fmt.Println("no case ok")}}Copy the code

Output: No case OK Sets a timeout of 2s in the third case statement, within which any other operable channel will execute the case.

Empty the select

package main

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

We know that the SELECT statement blocks until there is a case to operate on. But an empty SELECT statement has no case branch, so it keeps blocking and causes a deadlock. Error:

fatal error: all goroutines are asleep - deadlock!
goroutine 1 [select (no cases)]
Copy the code

Hope this article helps you, Good Day!


Original article, if need to be reproduced, please indicate the source! Check out “Golang is coming” or go to seekload.net for more great articles.

Prepare for you to learn Go language related books, public number background reply [ebook] receive!