一文掌握Go语言Select语句的用法

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第19天,点击查看活动详情

前言

相信大家对于switch并不陌生,然而selectswitch有个共同特性就是都通过 case的方式来处理,但是selectswitch处理的事情完全不同,也完全不相容,本文带大家认识Go语言的select用法,需要的朋友可以参考以下内容,希望对大家有帮助。

定义

select是Go中的一个控制结构,类似于通信的switch语句,每个case必须是一个通信操作,要么是发送要么是接收。

select随机执行一个可运行的case。如果没有case可运行,他将阻塞,直到有case可运行,一个默认的子句总是可运行的。

语法

以下是select语句的语法:

select {
    case <-ch1:
        // 如果从 ch1 信道成功接收数据,则执行该分支代码
    case ch2 <- 1:
        // 如果成功向 ch2 信道成功发送数据,则执行该分支代码
    default:
        // 如果上面都没有成功,则进入 default 分支处理流程
}
复制代码

可以看到select的语法结构有点类似于switch,但又有些不同。

select里的case后面并不带判断条件,而是一个信道的操作,不同于switch里的case。

注意事项:

  • select语句只能用于channel信道的IO操作,每个case都必须是一个通信。
  • 如果不设置default语句,当没有IO操作发生时,select语句就会一直阻塞,直到某个通信可以运行。
  • 如果有一个或多个IO操作时,Go运行会随机选择一个case执行,其他的不会执行,但此时无法保证执行顺序。
  • 对于case语句,如果存在信道值为nil的读写操作,则该分支将被忽略。
  • 对于空的select语句,会引起死锁。
  • 对于在for中的select语句,不能添加default,否则会引起cpu占用过高的问题。

示例

package main

import "fmt"

func main() {
   var c1, c2, c3 chan int
   var i1, i2 int
   select {
      case i1 = <-c1:
         fmt.Printf("received ", i1, " from c1\n")
      case c2 <- i2:
         fmt.Printf("sent ", i2, " to c2\n")
      case i3, ok := (<-c3):  // same as: i3, ok := <-c3
         if ok {
            fmt.Printf("received ", i3, " from c3\n")
         } else {
            fmt.Printf("c3 is closed\n")
         }
      default:
         fmt.Printf("no communication\n")
   }    
}
复制代码

典型

多个IO操作发生时,case语句是随机执行的

switch 里的 case 是顺序执行的,但在 select 里却不是。

func main() {
	ch1 := make(chan string, 1) // 创建 一个长度带缓冲的整型通道

	ch1 <- "aaaa" // 向通道中写入数据
	ch2 := make(chan string, 1)
	ch2 <- "bbbb"
	//多次执行后,会随机打印 “ch1 read” 或 “ch2 read”
	select {
	case <-ch1:
		fmt.Println("ch1 read")
	case <-ch2:
		fmt.Println("ch2 read")
	}
}
复制代码

空select语句会引发死锁

func main() {
	select {
	
	}
}
复制代码

避免造成死锁

如果在遍历完所有的 case 后,若没有可执行的任何一个 case 表达式,就会进入 default 里的代码分支。

如果没有写 default 分支,select 就会阻塞,直到有某个 case 可以命中,而如果一直没有命中,select 就会抛出 deadlock 的错误。

解决:

养成好习惯,在 select 的时候,也写好 default 分支代码,尽管你 default 下没有写任何代码。

超时实现

func main() {
	quit := make(chan bool)
	ch := make(chan int)
	go func() {
		for {
			select {
			case num := <-ch:
				fmt.Println("num = ", num)
			case <-time.After(5 * time.Second):
				fmt.Println("超时")
				quit <- true
			}
		}
	}()
	for i := 0; i < 2; i++ {
		ch <- i
		time.Sleep(time.Second)
	}
	<-quit // 等待超时后, 结束 main主线程
	fmt.Println("程序结束")
}
复制代码

总结

select 与 switch 原理很相似,但它的使用场景更特殊,学习了本篇文章,你需要知道如下几点区别:

  • select 只能用于 channel 的操作(写入/读出),而 switch 则更通用一些;
  • select 的 case 是随机的,而 switch 里的 case 是顺序执行;
  • select 要注意避免出现死锁,同时也可以自行实现超时机制;
  • select 里没有类似 switch 里的 fallthrough 的用法;
  • select 不能像 switch 一样接函数或其他表达式;

猜你喜欢

转载自juejin.im/post/7132025141122301960