导读
select
是一种go可以处理多个通道之间的机制,看起来和switch
语句很相似,但是select
其实和IO机制中的select一样,多路复用通道,随机选取一个进行执行,如果说通道(channel)实现了多个goroutine之前的同步或者通信,那么select
则实现了多个通道(channel)的同步或者通信,并且select具有阻塞的特性。
示例
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan int)
ch2 := make(chan int)
go func1 () {
time.Sleep(time.Second)
ch1 <- 1
}()
go func2 () {
ch2 <- 3
}()
select {
case i := <-ch1:
fmt.Printf("从ch1读取了数据%d", i)
case j := <-ch2:
fmt.Printf("从ch2读取了数据%d", j)
}
}
上面这段代码很简单,我们创建了两个无缓冲的channel,通过两个goroutine向ch1
,ch2
两个通道发送数据,通过select随机读取ch1
,ch2
的返回值,但是由于func1
有sleep,所以这个例子我们总是从ch2
读到结果,打印从ch2读取了数据3
场景
select这个特性到底有什么用呢,下面我们来介绍一些使用select的场景
- 竞争选举
select {
case i := <-ch1:
fmt.Printf("从ch1读取了数据%d", i)
case j := <-ch2:
fmt.Printf("从ch2读取了数据%d", j)
case m := <- ch3
fmt.Printf("从ch3读取了数据%d", m)
...
}
这个是最常见的使用场景,多个通道,有一个满足条件可以读取,就可以“竞选成功”
- 超时处理(保证不阻塞)
select {
case str := <- ch1
fmt.Println("receive str", str)
case <- time.After(time.Second * 5):
fmt.Println("timeout!!")
}
因为select是阻塞的,我们有时候就需要搭配超时处理来处理这种情况,超过某一个时间就要进行处理,保证程序不阻塞。
- 判断buffered channel是否阻塞
package main
import (
"fmt"
"time"
)
func main() {
bufChan := make(chan int, 5)
go func () {
time.Sleep(time.Second)
for {
<-bufChan
time.Sleep(5*time.Second)
}
}()
for {
select {
case bufChan <- 1:
fmt.Println("add success")
time.Sleep(time.Second)
default:
fmt.Println("资源已满,请稍后再试")
time.Sleep(time.Second)
}
}
}
这个例子很经典,比如我们有一个有限的资源(这里用buffer channel实现),我们每一秒向bufChan
传送数据,由于生产者的生产速度大于消费者的消费速度,故会触发default
语句,这个就很像我们web端来显示并发过高的提示了,小伙伴们可以尝试删除go func
中的time.Sleep(5*time.Second)
,看看是否还会触发default
语句
- 阻塞main函数
有时候我们会让main函数阻塞不退出,如http服务,我们会使用空的select{}来阻塞main goroutine
package main
import (
"fmt"
"time"
)
func main() {
bufChan := make(chan int)
go func() {
for{
bufChan <-1
time.Sleep(time.Second)
}
}()
go func() {
for{
fmt.Println(<-bufChan)
}
}()
select{}
}
如上所示,这样主函数就永远阻塞住了,这里要注意上面一定要有一直活动的goroutine,否则会报deadlock
。大家还可以把select{}
换成for{}
试一下,打开系统管理器看下CPU的占用变化。
网友评论