Go-select语句详解

1、什么是select语句?

select 是 Go 中的一个控制结构。select 语句类似于 switch 语句,但是select会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。

2、select语句的作用?

当我们需要同时从多个通道接收数据时,如果通道里没有数据将会发生阻塞。为了应对这种场景,Go内置了select关键字,可以同时响应多个通道的操作。

3、select语句的语法结构
select {
    case 表达式1:
       statement(s);      
    case 表达式2:
       statement(s); 
    default : /* 可选 */
       statement(s);
}

注意:它仅能用于channel的相关操作,select 里的 case 表达式要求是对信道的操作

示例:

package main

import (
	"fmt"
)

func main() {
	c1 := make(chan string, 1)
	c2 := make(chan string, 1)

	c2 <- "hello"

	select {
	case msg1 := <-c1:
		fmt.Println("c1 received: ", msg1)
	case msg2 := <-c2:
		fmt.Println("c2 received: ", msg2)
	default:
		fmt.Println("No data received.")
	}
}

输出:

c2 received:  hello

select语句的处理逻辑:case随机选择处理列出的多个通信情况中的一个

  • 如果都阻塞了,会等待直到其中一个可以处理
  • 如果多个可以处理,随机选择一个
  • 为了避免死锁,应该编写default分支或手动实现超时机制,否则select 整体就会一直阻塞

超时示例:

package main

import (
	"fmt"
	"time"
)

func main() {
	channel1 := make(chan int)
	channel2 := make(chan int)

	select {
	case <-channel1:
		fmt.Println("=====channel1=====")
	case <-channel2:
		fmt.Println("=====channel1=====")
	case <-time.After(3 * time.Second):
		fmt.Println("====timeout====")
		//or
		//default:
		//  fmt.Println("====default====")
	}
}

输出:

====timeout====
4、select与switch的区别
  • select 的 case 是随机的,而 switch 里的 case 是顺序执行;
  • select 里没有类似 switch 里的 fallthrough 的用法;
  • select 里的 case 表达式要求是对信道的操作,不能像 switch 一样接函数或其他表达式;
5、select的应用场景
  • 生产者消费者模式
package main

import (
	"fmt"
	"time"
)

func main() {
	ch1 := make(chan int)
	ch2 := make(chan int)

	go produce1(ch1)
	go produce2(ch2)
	go consume(ch1, ch2)

	time.Sleep(1e9)
}

func produce1(ch chan int) {
	for i := 0; ; i++ {
		ch <- i * 2
	}
}

func produce2(ch chan int) {
	for i := 0; ; i++ {
		ch <- i + 5
	}
}

func consume(ch1, ch2 chan int) {
	for {
		select {
		case v := <-ch1:
			fmt.Printf("data from channel 1: %d\n", v)
		case v := <-ch2:
			fmt.Printf("data from channel 2: %d\n", v)
		}
	}
}

输出(随机交替打印知道main程序到时退出):

...
data from channel 1: 82550
data from channel 1: 82552
data from channel 1: 82554
data from channel 1: 82556
data from channel 1: 82558
data from channel 1: 82560
data from channel 1: 82562
data from channel 1: 82564
data from channel 1: 82566
data from channel 1: 82568
data from channel 1: 82570
data from channel 1: 82572
...
  • 用于判断管道是否存满
package main

import (
	"fmt"
	"time"
)

func main() {
	output1 := make(chan string, 10)
	go write(output1)
	for s := range output1 {
		fmt.Println("res:", s)
		time.Sleep(3*time.Second)
	}
}

func write(ch chan string) {
	for {
		select {
		// 写数据
		case ch <- "hello":
			fmt.Println("write hello")
		default:
			fmt.Println("channel full")
		}
		time.Sleep(time.Millisecond * 500)
	}
}

输出:

write hello
res: hello
write hello
write hello
write hello
write hello
write hello
res: hello
write hello
write hello
write hello
write hello
write hello
write hello
res: hello
write hello
channel full
channel full
channel full
channel full
channel full
res: hello
write hello
channel full
...

本文参考了
The way to go-使用select切换协程
Go时光编程-理解select用法
ToGopher-select多路复用

你可能感兴趣的:(Golang)