Hi,你们好,我是明哥。html
在本身学习 Golang 的这段时间里,我写了详细的学习笔记放在个人我的微信公众号 《Go编程时光》,对于 Go 语言,我也算是个初学者,所以写的东西应该会比较适合刚接触的同窗,若是你也是刚学习 Go 语言,不防关注一下,一块儿学习,一块儿成长。git
个人在线博客:http://golang.iswbm.com
个人 Github:github.com/iswbm/GolangCodingTimegithub
前面写过两节关于 switch-case
的文章,分别是:golang
今天要学习一个跟 switch-case
很像,但还有点我的特点
的 select-case
,这一节本应该放在 学习 Go 协程:详解信道/通道 里一块儿讲的,可是当时漏了,直到有读者给我提出,才注意到,今天就用这篇文章补充一下。微信
跟 switch-case 相比,select-case 用法比较单一,它仅能用于 信道/通道 的相关操做。函数
select { case 表达式1: <code> case 表达式2: <code> default: <code> }
接下来,咱们来看几个例子帮助理解这个 select 的模型。学习
先建立两个信道,并在 select 前往 c2 发送数据3d
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.") } }
在运行 select 时,会遍历全部(若是有机会的话)的 case 表达式,只要有一个信道有接收到数据,那么 select 就结束,因此输出以下
c2 received: hello
select 在执行过程当中,必须命中其中的某一分支。
若是在遍历完全部的 case 后,若没有命中(命中
:也许这样描述不太准确,我本意是想说能够执行信道的操做语句)任何一个 case 表达式,就会进入 default 里的代码分支。
但若是你没有写 default 分支,select 就会阻塞,直到有某个 case 能够命中,而若是一直没有命中,select 就会抛出 deadlock
的错误,就像下面这样子。
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.") } }
运行后输出以下
fatal error: all goroutines are asleep - deadlock! goroutine 1 [select]: main.main() /Users/MING/GolandProjects/golang-test/main.go:13 +0x10f exit status 2
解决这个问题的方法有两种
一个是,养成好习惯,在 select 的时候,也写好 default 分支代码,尽管你 default 下没有写任何代码。
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: } }
另外一个是,让其中某一个信道能够接收到数据
package main import ( "fmt" "time" ) func main() { c1 := make(chan string, 1) c2 := make(chan string, 1) // 开启一个协程,能够发送数据到信道 go func() { time.Sleep(time.Second * 1) c2 <- "hello" }() select { case msg1 := <-c1: fmt.Println("c1 received: ", msg1) case msg2 := <-c2: fmt.Println("c2 received: ", msg2) } }
以前学过 switch 的时候,知道了 switch 里的 case 是顺序执行的,但在 select 里却不是。
经过下面这个例子的执行结果就能够看出
当 case 里的信道始终没有接收到数据时,并且也没有 default 语句时,select 总体就会阻塞,可是有时咱们并不但愿 select 一直阻塞下去,这时候就能够手动设置一个超时时间。
package main import ( "fmt" "time" ) func makeTimeout(ch chan bool, t int) { time.Sleep(time.Second * time.Duration(t)) ch <- true } func main() { c1 := make(chan string, 1) c2 := make(chan string, 1) timeout := make(chan bool, 1) go makeTimeout(timeout, 2) select { case msg1 := <-c1: fmt.Println("c1 received: ", msg1) case msg2 := <-c2: fmt.Println("c2 received: ", msg2) case <-timeout: fmt.Println("Timeout, exit.") } }
输出以下
Timeout, exit.
上面例子里的 case,好像都只从信道中读取数据,但实际上,select 里的 case 表达式只要求你是对信道的操做便可,无论你是往信道写入数据,仍是从信道读出数据。
package main import ( "fmt" ) func main() { c1 := make(chan int, 2) c1 <- 2 select { case c1 <- 4: fmt.Println("c1 received: ", <-c1) fmt.Println("c1 received: ", <-c1) default: fmt.Println("channel blocking") } }
输出以下
c1 received: 2 c1 received: 4
select 与 switch 原理很类似,但它的使用场景更特殊,学习了本篇文章,你须要知道以下几点区别:
系列导读
24. 超详细解读 Go Modules 前世此生及入门使用