Select

select 语句用在多个发送/接收通道操作中进行选择。

  • select 语句会一直阻塞,直到发送/接收操作准备就绪。

  • 如果有多个通道操作准备完毕, select 会随机地选取其中之一执行。

例子:

程序创建了 3 个通道,并在执行 select 语句之前往通道 1 、通道 2 和 通道 3 分别发送数据,在执行 select 语句时,如果有机会的话会运行所有表达式,只要其中一个通道接收到数据,那么就会执行对应的 case 代码,然后退出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import "fmt"

func main() {
// 创建三个通道
ch1 := make(chan string, 1)
ch2 := make(chan string, 1)
ch3 := make(chan string, 1)

// 往通道发送数据
ch1 <- "111"
ch2 <- "222"
ch3 <- "333"

select {
case msg1 := <-ch1:
fmt.Println("ch1 received: ", msg1)
case msg2 := <-ch2:
fmt.Println("ch2 received: ", msg2)
case msg3 := <-ch3:
fmt.Println("ch3 received: ", msg3)

}
}


select 的应用

每个任务执行的时间不同,使用 select 语句等待相应的通道发出响应。select 会选择首先响应先完成的 task,而忽略其它的响应。使用这种方法,我们可以做多个 task,并给用户返回最快的 task 结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package main

import (
"fmt"
"time"
)

func task1(ch chan string) {
time.Sleep(5 * time.Second)
ch <- "111"
}

func task2(ch chan string) {
time.Sleep(7 * time.Second)
ch <- "222"
}

func task3(ch chan string) {
time.Sleep(2 * time.Second)
ch <- "333"
}

func main() {
// 创建两个通道
ch1 := make(chan string)
ch2 := make(chan string)
ch3 := make(chan string)
go task1(ch1)
go task2(ch2)
go task3(ch3)

select {
// 如果从通道 1 收到数据
case message1 := <-ch1:
fmt.Println("ch1 received:", message1)
// 如果从通道 2 收到数据
case message2 := <-ch2:
fmt.Println("ch2 received:", message2)
// 如果从通道 3 收到数据
case message3 := <-ch3:
fmt.Println("ch3 received:", message3)
}
}

因为如果加了该默认分支,如果还没从通道接收到数据, select 语句就会直接执行 default 分支然后退出,而不是被阻塞。

select 由多个 case 准备就绪时,将会随机地选取其中之一去执行。

select 超时处理

case 里的通道始终没有接收到数据时,而且也没有 default 语句时, select 整体就会阻塞,但是有时我们并不希望 select 一直阻塞下去,这时候就可以手动设置一个超时时间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import (
"fmt"
"time"
)

func makeTimeout(ch chan bool, t int) {
time.Sleep(time.Second * time.Duration(t))
ch <- true
}

func main() {
c1 := make(chan string, 1)
c2 := make(chan string, 1)
c3 := make(chan string, 1)
timeout := make(chan bool, 1)

go makeTimeout(timeout, 2)

select {
case msg1 := <-c1:
fmt.Println("c1 received: ", msg1)
case msg2 := <-c2:
fmt.Println("c2 received: ", msg2)
case msg3 := <-c3:
fmt.Println("c3 received: ", msg3)
case <-timeout:
fmt.Println("Timeout, exit.")
}
}

读取/写入数据

select 里的 case 表达式只能对通道进行操作,不管你是往通道写入数据,还是从通道读出数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
)

func main() {
c1 := make(chan string, 2)

c1 <- "111"
select {
case c1 <- "222":
fmt.Println("c1 received: ", <-c1)
fmt.Println("c1 received: ", <-c1)
default:
fmt.Println("channel blocking")
}
}