30《Go语言入门》select和channel

这是我纯手写的《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异常)

支持🤟


  • 🎸 [关注❤️我吧],我会持续更新的。
  • 🎸 [点个👍赞吧],码字不易麻烦了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值