管道选择器(select)
什么是select
Golang中select
用来从多个channel
中选择性的获取数据,这里的选择性主要是指的快慢顺序,哪个channel
先收到数据,就读取哪个channel
中的数据。如果多个channel
同时准备好了数据,那就随机选取一个。select
语句的语法跟switch
语句非常像,只是case
语句是对channel
的读取语句。下面我们通过一个例子来理解下。
举例
这里我们创建了两个不同的channel
,output1
以及output2
。并创建了两个协程,对应server1
和server2
。其中server1
中过了6秒向channel output1
中写入了数据,server2
中过了2秒向channel output2
中写入了数据。
主协程中的select
语句会阻塞主协程的执行,直到某个case
语句读取到数据,由于server2
先向channel
中写入数据,因此select
语句先读取到server2
中写入的数据。该程序输出如下:
如果了解Node.js
的话你会发现,Golang中的select
其实跟Node.js
中的Promise.race
功能差不多。
select真实适用场景
我们在前面一段程序中是特意将两个协程任务命名为server1
和server2
的,就是为了模拟真实应用场景。
假设我们有个应用对性能要求极其苛刻,我们需要尽可能快的返回数据。而数据是备份在不同地区的多个服务器上。我们假设前面的server1
和server2
分别去两个不同的数据库服务器上拉取数据。那么到底哪个数据库服务器先返回数据取决于各个服务器的负载和网络情况。由于我们不知道哪个服务先返回,因此我们同时创建两个服务,通过select
,我们可以选择先返回的数据,而后返回的数据则抛掉不用。
默认case
跟switch
语句类型,我们也可以给select
语句提供默认case
。默认case
会在其他case
都读取不到数据时执行,默认case
其实主要是为了防止select
语句阻塞。
这里我们为process
函数创建了一个协程,该协程内暂停了10秒多点时间,然后将字符串"process successful"
写入了channel
。
创建完该协程后,主协程进入了一个无限循环。每隔1秒钟,调用select
语句读取channel ch
中的数据,或者执行默认case
,由于前面10秒内channel ch
内都没写入数据,因此会执行默认case
,打印10次no value received
。但是第10秒到第11秒的那次循环就从channel ch
内读到了数据,接着打印并退出主协程。该程序输出如下:
死锁
这里我们创建了一个channel ch
。我们在select
语句中尝试从ch
中读数据。但是由于没有其他goroutine
像该channel中写入数据,因此会陷入死锁状态。这里需要注意,虽然后面有写入数据的语句ch <- "Hello"
,但是由于select
语句已经阻塞了,因此已经是死锁状态了,后面的语句根本没机会执行,必须是其他协程向ch
中写入数据才行。该程序会报如下运行时错误:
当然如果我们提供了默认case
语句,那么就不会陷入死锁状态了。比如如下程序:
该程序输出为:default case executed
。
这里再补充一点,即使创建的ch
未手动初始化,即为nil
,只要提供了默认case
,也不会阻塞。
这里什么的channel ch
为nil
。这里select
的case
语句v := <-ch
,永远读取不到数据,相当于陷入了死锁状态。但是由于提供了默认case
语句,因此也不会阻塞。该程序输出:default case executed
。
随机选择
当select
语句中多个case
都ready时,会随机选取一个。
这里我们还是创建了两个协程server1
和server2
,在这两个协程内部都是立即向channel中写入了数据。主协程暂停了1秒钟,然后执行select
语句。执行select
语句时由于两个channel的数据均以准备好,因此两个case
语句均判断通过,这时随机选取一个case
分支进行执行。我们本机运行会看到,该程序多次允许的输出结果会在两个case
中随机出现。
当心空select
语句
select
语句如果select
语句为空,即不含有一个case
语句,也会进入死锁状态。
该程序报如下运行时错误:
Last updated
Was this helpful?