这是我纯手写的《Go语言入门》,手把手教你入门Go。源码+文章,看了你就会🥴!
文章中所有的代码我都放到了github.com/GanZhiXiong/go_learning这个仓库中!
看文章时,对照仓库中代码学习效果更佳哦!
目录
select
select能够让goroutine同时等待多个channel可读或可写,在多个文件或channel状态改变之前,select会一直阻塞当前线程或goroutine。
select 是与 switch 相似的控制结构,与 switch 不同的是,select 中虽然也有多个 case,但是这些 case 中的表达式必须都是 Channel 的收发操作。
select结构:
select {
case <-chan1:
// 如果chan1成功读到数据,则进行该case处理语句
case chan2 <- 1:
// 如果成功向chan2写入数据,则进行该case处理语句
default:
// 如果上面都没有成功,则进入default处理流程
}
作用
用于监听channel的IO操作,以便从不同的并发执行的协程中获取值。
非阻塞的收发
- 当存在可以收发的 channel 时,直接处理该 channel 对应的 case;
- 当不存在可以收发的 channel 时,有default,执行 default 中的语句,否者select会阻塞;
比如下面的代码就不会阻塞当前goroutine
func TestSelect(t *testing.T) {
ch := make(chan int)
select {
case i := <-ch:
t.Log(i)
default:
t.Log("default")
}
}
随机执行
当select遇到多个case同时满足时,会随机选择一个case执行。
// 随机执行
func TestSelect2(t *testing.T) {
ch := make(chan int)
go func() {
for range time.Tick(1 * time.Second) {
ch <- 0
}
}()
for {
select {
case <-ch:
t.Log("case1")
case <-ch:
t.Log("case2")
}
}
}
超时
有时候会出现goroutine阻塞的情况,那么我们如何避免整个程序进入阻塞的情况呢?
我们可以利用select来设置超时,通过如下的方式实现:
示例1:
func TestSelect5(t *testing.T) {
t.Log(time.Now())
ch := make(chan int)
go func(c chan int) {
time.Sleep(time.Second * 3)
ch <- 1
}(ch)
select {
case v := <-ch:
t.Log(time.Now())
t.Log(v)
case <-time.After(2 * time.Second): // 等待2秒
t.Log(time.Now())
t.Log("超时")
}
time.Sleep(time.Second * 5)
}
=== RUN TestSelect5
30_select_test.go:74: 2021-03-12 14:37:45.911066 +0800 CST m=+0.000517798
30_select_test.go:87: 2021-03-12 14:37:47.916287 +0800 CST m=+2.005678829
30_select_test.go:88: 超时
--- PASS: TestSelect5 (12.01s)
PASS
示例2:
func TestSelect3(t *testing.T) {
ch := make(chan int)
quit := make(chan bool)
go func() {
for {
select {
case num := <-ch:
t.Log("num = ", num)
case <-time.After(3 * time.Second):
t.Log("超时")
quit <- true
}
}
}()
for i := 0; i < 5; i++ {
ch <- i
time.Sleep(time.Second)
}
<- quit
t.Log("程序结束")
}
空select{}会引起死锁
func TestSelect6(t *testing.T) {
select {
}
}
=== RUN TestSelect6
fatal error: all goroutines are asleep - deadlock!
case语句中,如存在通道值为nil的读写操作,则该分支被忽略
因为ch为nil,所以编译器会警告Receive may block because of 'nil' channel
,
运行则会报死锁错误。
func TestSelect8(t *testing.T) {
var ch chan int
//ch = make(chan int)
select {
// Receive may block because of 'nil' channel
case <-ch:
t.Log("ok")
//default:
// t.Log("ll")
}
}
检测chan是否已满
func TestSelect10(t *testing.T) {
ch := make(chan int, 1)
ch <- 1
select {
case ch <- 2:
default:
t.Log("channel is full!")
}
}
其他示例
使用select生成偶数
func TestSelect4(t *testing.T) {
ch := make(chan int, 1)
for i := 0; i < 100; i++ {
select {
case x := <-ch:
t.Log(x)
case ch <- i:
}
}
}
使用select生成随机数
只需将上题中的ch的缓冲大小从1改为大于1的任意数即可。
注意:如果ch缓冲大小为0,将会永远阻塞(报deadlock异常)
支持🤟
- 🎸 [关注❤️我吧],我会持续更新的。
- 🎸 [点个👍赞吧],码字不易麻烦了。