非零基础自学Golang
第13章 并发与通道
13.4 select
Go语言中,通过关键字select可以监听channel上的数据流动。
select的用法和switch非常相似,由select开始一个新的选择块,每个选择条件由case语句来描述。
13.4.1 select作用
与switch语句可以选择任何可使用相等比较的条件相比,select有较多的限制,其中最大的限制就是每个case语句里面必须是一个I/O操作,大致结构如下:
select {
case <-chan1:
// 如果chan1成功读到数据,则执行该case语句
case chan2 <- 1:
// 如果成功向chan2写入数据,则执行该case语句
default:
//如果上面的case都没有执行成功,则执行该default语句
}
在一个select语句中,Go语言会按顺序从头至尾评估每一个发送和接收语句,如果其中的任意一个语句可以继续执行(没有被阻塞),那么就从那些可以执行的语句中随机选择一条来使用。
如果没有任何一条语句可以执行(即所有通道都被阻塞),就会默认执行default语句,同时程序的执行会从select语句后的语句中恢复。如果没有default语句,则select语句将被阻塞,直到有一个channel可以进行下去。
[ 动手写 13.4.1]
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
go func() {
for i := 0; i < 3; i++ {
ch <- i
}
}()
for {
select {
case msg := <-ch:
fmt.Println(msg)
default:
time.Sleep(time.Second)
}
}
}
运行结果
13.4.2 超时
有时候会出现goroutine阻塞的情况,为了避免程序长时间进入阻塞,我们可以使用select来实现阻塞超时机制,通过如下方法实现:
[动手写 13.4.2 ]
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
done := make(chan bool)
go func() {
for {
select {
case val := <-ch:
fmt.Println(val)
case <-time.After(time.Second * 3):
fmt.Println("已超时3秒")
done <- true
}
}
}()
for i := 0; i < 10; i++ {
ch <- i
}
<-done
fmt.Println("程序终止")
}
运行结果:
13.4.3 死锁
在编写并发程序时可能会碰到死锁。
什么是死锁?就是所有的线程或进程都在等待资源的释放,举个例子:
[动手写 13.4.3]
package main
func main() {
ch := make(chan int)
<-ch // 阻塞main goroutine, 信道ch 被锁
}
动手写13.4.3中只有一个goroutine,我们向里面加数据或者存数据的话,都会锁死信道,并且阻塞当前goroutine,也就是所有的goroutine(其实只有main线程一个)都在等待信道的开放(没人拿走数据的话,信道是不会开放的),这就产生了死锁。
在非缓冲信道上如果发生了流入无流出,或者流出无流入,都会导致死锁。
同样地,使用select关键字,其中不加任何代码也会产生死锁。
[ 动手写 13.4.4 ]
package main
func main() {
select {
}
}
运行结果
13.5 小结
- 了解并行和并发的区别。
- 掌握goroutine的创建和使用。
- 掌握channel的创建和使用,以及多个goroutine之间的通信。
- 掌握如何使用runtime包对goroutine进行调度。
- 掌握channel的缓冲机制。
- 掌握Go关键字select的常见用法。