1. 条件语句select

1.1.1. select 语句

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

select 是Go中的一个控制结构,类似于用于通信的switch语句。每个case必须是一个通信操作,要么是发送要么是接收。 select 随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。一个默认的子句应该总是可运行的。

语法

Go 编程语言中 select 语句的语法如下:

  1. select {
  2. case communication clause :
  3. statement(s);
  4. case communication clause :
  5. statement(s);
  6. /* 你可以定义任意数量的 case */
  7. default : /* 可选 */
  8. statement(s);
  9. }

以下描述了 select 语句的语法:

  1. 每个case都必须是一个通信
  2. 所有channel表达式都会被求值
  3. 所有被发送的表达式都会被求值
  4. 如果任意某个通信可以进行,它就执行;其他被忽略。
  5. 如果有多个case都可以运行,Select会随机公平地选出一个执行。其他不会执行。
  6. 否则:
  7. 如果有default子句,则执行该语句。
  8. 如果没有default字句,select将阻塞,直到某个通信可以运行;Go不会重新对channel或值进行求值。

实例:

  1. package main
  2. import "fmt"
  3. func main() {
  4. var c1, c2, c3 chan int
  5. var i1, i2 int
  6. select {
  7. case i1 = <-c1:
  8. fmt.Printf("received ", i1, " from c1\n")
  9. case c2 <- i2:
  10. fmt.Printf("sent ", i2, " to c2\n")
  11. case i3, ok := (<-c3): // same as: i3, ok := <-c3
  12. if ok {
  13. fmt.Printf("received ", i3, " from c3\n")
  14. } else {
  15. fmt.Printf("c3 is closed\n")
  16. }
  17. default:
  18. fmt.Printf("no communication\n")
  19. }
  20. }

以上代码执行结果为:

  1. no communication

select可以监听channel的数据流动

select的用法与switch语法非常类似,由select开始的一个新的选择块,每个选择条件由case语句来描述

与switch语句可以选择任何使用相等比较的条件相比,select由比较多的限制,其中最大的一条限制就是每个case语句里必须是一个IO操作

  1. select { //不停的在这里检测
  2. case <-chanl : //检测有没有数据可以读
  3. //如果chanl成功读取到数据,则进行该case处理语句
  4. case chan2 <- 1 : //检测有没有可以写
  5. //如果成功向chan2写入数据,则进行该case处理语句
  6. //假如没有default,那么在以上两个条件都不成立的情况下,就会在此阻塞//一般default会不写在里面,select中的default子句总是可运行的,因为会很消耗CPU资源
  7. default:
  8. //如果以上都没有符合条件,那么则进行default处理流程
  9. }

在一个select语句中,Go会按顺序从头到尾评估每一个发送和接收的语句。

如果其中的任意一个语句可以继续执行(即没有被阻塞),那么就从那些可以执行的语句中任意选择一条来使用。 如果没有任意一条语句可以执行(即所有的通道都被阻塞),那么有两种可能的情况: ①如果给出了default语句,那么就会执行default的流程,同时程序的执行会从select语句后的语句中恢复。 ②如果没有default语句,那么select语句将被阻塞,直到至少有一个case可以进行下去。

1.1.2. Golang select的使用及典型用法

基本使用

select是Go中的一个控制结构,类似于switch语句,用于处理异步IO操作。select会监听case语句中channel的读写操作,当case中channel读写操作为非阻塞状态(即能读写)时,将会触发相应的动作。 select中的case语句必须是一个channel操作

select中的default子句总是可运行的。

如果有多个case都可以运行,select会随机公平地选出一个执行,其他不会执行。

如果没有可运行的case语句,且有default语句,那么就会执行default的动作。

如果没有可运行的case语句,且没有default语句,select将阻塞,直到某个case通信可以运行

例如:

  1. package main
  2. import "fmt"
  3. func main() {
  4. var c1, c2, c3 chan int
  5. var i1, i2 int
  6. select {
  7. case i1 = <-c1:
  8. fmt.Printf("received ", i1, " from c1\n")
  9. case c2 <- i2:
  10. fmt.Printf("sent ", i2, " to c2\n")
  11. case i3, ok := (<-c3): // same as: i3, ok := <-c3
  12. if ok {
  13. fmt.Printf("received ", i3, " from c3\n")
  14. } else {
  15. fmt.Printf("c3 is closed\n")
  16. }
  17. default:
  18. fmt.Printf("no communication\n")
  19. }
  20. }
  21. //输出:no communication

1.1.3. 典型用法

1.超时判断

  1. //比如在下面的场景中,使用全局resChan来接受response,如果时间超过3S,resChan中还没有数据返回,则第二条case将执行
  2. var resChan = make(chan int)
  3. // do request
  4. func test() {
  5. select {
  6. case data := <-resChan:
  7. doData(data)
  8. case <-time.After(time.Second * 3):
  9. fmt.Println("request time out")
  10. }
  11. }
  12. func doData(data int) {
  13. //...
  14. }

2.退出

  1. //主线程(协程)中如下:
  2. var shouldQuit=make(chan struct{})
  3. fun main(){
  4. {
  5. //loop
  6. }
  7. //...out of the loop
  8. select {
  9. case <-c.shouldQuit:
  10. cleanUp()
  11. return
  12. default:
  13. }
  14. //...
  15. }
  16. //再另外一个协程中,如果运行遇到非法操作或不可处理的错误,就向shouldQuit发送数据通知程序停止运行
  17. close(shouldQuit)

3.判断channel是否阻塞

  1. //在某些情况下是存在不希望channel缓存满了的需求的,可以用如下方法判断
  2. ch := make (chan int, 5)
  3. //...
  4. data:=0
  5. select {
  6. case ch <- data:
  7. default:
  8. //做相应操作,比如丢弃data。视需求而定
  9. }