Select¶
select是什么¶
select
是Go语言层面提供的一种多路复用机制,用于检测当前goroutine
连接的多个channel
是否有数据准备完毕,可用于读或写。
IO多路复用¶
看到select
,很自然的会联想到Linux提供的IO多路复用模型:select
、poll
、epoll
,IO复用主要用于提升程序处理IO事件的性能。Go语言中的select
与Linux中的select
有一定的区别。操作系统中的IO多路复用简单理解就是用一个或者是少量线程处理多个IO事件。
传统阻塞IO vs IO多路复用¶
传统阻塞IO¶
对于每一个网络IO事件,操作系统都会起一个线程去处理,在IO事件没准备好的时候,当前线程就会一直阻塞。
优缺点:
- 优点:逻辑简单,在阻塞等待期间线程会挂起,不会占用 CPU 资源
- 缺点:每个连接需要独立的线程单独处理,当并发请求量大时为了维护程序,内存、线程切换开销较大
IO多路复用¶
IO多路复用的基本原理如下图所示:
优缺点: - 优点:通过复用一个线程处理了多个IO事件,无需对额外过多的线程维护管理,资源和效率上都获得了提升 - 缺点:当连接数较少时效率相比多线程+阻塞 I/O 模型效率较低
Go语言的select
语句,是用来起一个goroutine
监听多个Channel
的读写事件,提高从多个Channel
获取信息的效率,相当于也是单线程处理多个IO事件,其思想基本相同。
select用法¶
select
的基本使用模式如下:
Go | |
---|---|
select
的用法形式类似于switch
,但是区别于switch
的是,select
各个case
的表达式必须都是channel
的读写操作。select
通过多个case
语句监听多个channel
的读写操作是否准备好可以执行,其中任何一个case
可以执行了则选择该case
语句执行,如果没有可以执行的case
,则执行default
语句,如果没有default
,则当前goroutine
会阻塞。
空select永久阻塞¶
当一个select
中什么语句都没有,没有任何case
,将会永久阻塞:
Text Only | |
---|---|
select
语句导致永久阻塞,当前goroutine
阻塞之后,由于Go语言自带死锁检测机制,发现当前goroutine
永远不会被唤醒,会报上述死锁错误。
没有default且case无法执行的select永久阻塞¶
看下面示例:
Go | |
---|---|
Text Only | |
---|---|
select
从两个channel
,ch1
和ch2
中读取数据,但是两个channel
都没有数据,且没有goroutine
往里面写数据,所以不可能读到数据,这两个case
永远无法执行到,select
也没有default
,所以会出现永久阻塞,报死锁。
有单一case和default的select¶
Go | |
---|---|
Text Only | |
---|---|
select
语句的时候,由于ch
中没有数据,且没有goroutine
往channel
中写数据,所以case
不可能执行到,就会执行default
语句,打印出default!!!
。
有多个case和default的select¶
运行结果:
主
goroutine
中向后往管道ch1
和ch2
中发送数据,在子goroutine
中执行两个select
,可以看到,在执行select
的时候,那个case
准备好了就会执行当下case
的语句,最后没有数据可接受了,没有case
可以执行,则执行default
语句。
注意:当多个case都准备好了的时候,会随机选择一个执行
Go | |
---|---|
Text Only | |
---|---|
case
都有可能打印,这就是select
选择的随机性。