什么是 select?
select 语句用于从多个发送/接收 channel 操作中进行选择。  select 语句将阻塞,直到其中一个发送/接收操作准备就绪。如果准备好多个操作,则随机选择其中一个操作。语法类似于 switch,但是每个 case 语句都是一个 channel 操作。让我们深入研究一些代码,以便更好地理解。
Example
package mainimport ("fmt""time")func server1(ch chan string) {time.Sleep(6 * time.Second)ch <- "from server1"}func server2(ch chan string) {time.Sleep(3 * time.Second)ch <- "from server2"}func main() {output1 := make(chan string)output2 := make(chan string)go server1(output1)go server2(output2)select {case s1 := <-output1:fmt.Println(s1)case s2 := <-output2:fmt.Println(s2)}}
在上面的程序中,第 8 行 server1 函数休眠 6 秒然后将文本 from server1 写入 channel ch。第 13 行 server2 函数休眠 3 秒,然后 from server2 写入 channel  ch。
main 函数在第 21 行和第 22 行分别调用 Goroutine server1 和 server2 。
在 23 行,程序到达 select 语句。select 语句将阻塞,直到其中一个 channel 就绪。在上面的程序中,server1 Goroutine 在 6 秒后写入 output1  channel ,而 server2 在 3 秒后写入 output2  channel 。因此 select 语句将阻塞 3 秒,并等待 server2   Goroutine 写入 output2  channel 。3 秒后,程序输出
from server2
然后程序终止。
select 的实际用途
将上述程序中的函数命名为 server1 和 server2 的原因是为了说明 select 的实际使用。
让我们假设我们有一个关键任务的应用程序,我们需要尽快将输出返回给用户。该应用程序的数据库被复制并存储在世界各地的不同服务器中。假设函数 server1 和 server2 实际上与 2 个这样的服务器通信。每个服务器的响应时间取决于每个服务器的负载和网络延迟。我们将请求发送到两个服务器,然后使用 select 语句在相应的 channel 上等待响应。首先响应的服务器由 select 选择,其他响应被忽略。这样我们就可以向多个服务器发送相同的请求,并将最快的响应返回给用户:)。
Default case
select 语句中的 default case 在其他情况都没有准备好时执行。这通常用于防止 select  语句阻塞。
package mainimport ("fmt""time")func process(ch chan string) {time.Sleep(10500 * time.Millisecond)ch <- "process successful"}func main() {ch := make(chan string)go process(ch)for {time.Sleep(1000 * time.Millisecond)select {case v := <-ch:fmt.Println("received value: ", v)returndefault:fmt.Println("no value received")}}}
在上面的程序中,第 8 行 process 函数休眠 10500 毫秒( 10.5 秒),然后将 process successful 写入 ch  channel 。这个函数在第 15 行被并发调用。
在同时调用 process Goroutine之后,在 main Goroutine 中启动了无限循环。无限循环在每次迭代开始期间休眠 1000 毫秒(1 秒),并执行选择操作。在前 10500 毫秒期间,select 语句的第一种情况即 case v := <-ch: 将不会准备就绪,因为Goroutine process 在10500毫秒后写入 ch  channel 。因此,在此期间将执行 default 语句,程序将输出 10 次 no value received。
在 10.5 秒之后,process Goroutine在第 10行 将 process successful写入 ch 。现在将执行 select 语句的第一种情况,程序将输出 received value:  process successful,然后终止。该程序将输出,
no value receivedno value receivedno value receivedno value receivedno value receivedno value receivedno value receivedno value receivedno value receivedno value receivedreceived value: process successful
死锁和 default case
package mainfunc main() {ch := make(chan string)select {case <-ch:}}
在上面的程序中,我们在第 4 行创建了一个 channel  ch。我们尝试从第 6 行中选择的这个 channel 读取。 select 语句将永远阻塞,因为没有其他 Goroutine 写入此 channel ,因此将导致死锁。该程序将在运行时发生报错,
fatal error: all goroutines are asleep - deadlock!goroutine 1 [chan receive]:main.main()/tmp/sandbox416567824/main.go:6 +0x80
如果存在 default case ,则不会发生此死锁,因为在没有其他情况准备就绪时将执行 default case 。上面的程序用下面的 default case 重写。
package mainimport "fmt"func main() {ch := make(chan string)select {case <-ch:default:fmt.Println("default case executed")}}
以上程序将输出
default case executed
类似地,即使 select 只有零 channel ,也会执行 default case 。
package mainimport "fmt"func main() {var ch chan stringselect {case v := <-ch:fmt.Println("received value", v)default:fmt.Println("default case executed")}}
在上面的程序中,ch 是nil,第 8 行我们试图从 ch 中的 select 中读取。如果default 情况不存在,则 select 将永远被阻塞并导致死锁。由于我们在 select 中有一个默认的情况,它将被执行并且程序将输出,
default case executed
随机选择
当 select 语句中的多个 case 已准备就绪时,其中一个将随机执行。
package mainimport ("fmt""time")func server1(ch chan string) {ch <- "from server1"}func server2(ch chan string) {ch <- "from server2"}func main() {output1 := make(chan string)output2 := make(chan string)go server1(output1)go server2(output2)time.Sleep(1 * time.Second)select {case s1 := <-output1:fmt.Println(s1)case s2 := <-output2:fmt.Println(s2)}}
在上面的程序中,server1 和 server2 在 18 19 行并发调用。然后主程序在第 21 行中休眠 1 秒。 当程序到达第 22 行中的 select 语句时。server1 将从 from server1 写入 output1  channel ,server2 将 from server2 写入 output2  channel ,因此 select 语句的两种情况都准备好执行。如果多次运行此程序,输出将在 from server1 或 from server2 之间变化,具体取决于随机选择的情况。
请在本地系统中运行此程序以获得此随机性。如果该程序在 playground 上运行,它将输出相同的输出,因为 playground 结果是固定的。
疑难杂症 —— 空 Select
package mainfunc main() {select {}}
你认为上面程序的输出结果是什么?
我们知道 select 语句将被阻塞,直到执行其中一个案例。在这种情况下,select 语句没有任何情况,因此它将永远阻塞导致死锁。这个程序运行会得到报错,
fatal error: all goroutines are asleep - deadlock!goroutine 1 [select (no cases)]:main.main()/tmp/sandbox299546399/main.go:4 +0x20
