NOTE
2.6 select
1. 是什么 - 类似于IO多路复用 - 监听多个阻塞的事件,如果事件发生了,那么执行该事件逻辑 - 如果没有发生并有默认的事件,那么执行默认的事件逻辑 - 如果没有发生且没有默认的事件,那么一直阻塞 2. 使用 2.1. 基本使用 代码执行到 select 时,case 语句会按照源代码的顺序被评
这是历史学习笔记,可能存在过时或不完整的理解。
1. 是什么
- 类似于IO多路复用
- 监听多个阻塞的事件,如果事件发生了,那么执行该事件逻辑
- 如果没有发生并有默认的事件,那么执行默认的事件逻辑
- 如果没有发生且没有默认的事件,那么一直阻塞
2. 使用
2.1. 基本使用
select {
case v1 := <-c1: //receive操作
fmt.Printf("received %v from c1\n", v1)
case v2 := <-c2: //receive操作
fmt.Printf("received %v from c2\n", v1)
case c3 <- 23: //send操作
fmt.Printf("sent %v to c3\n", 23)
default: //默认
fmt.Printf("no one was ready to communicate\n")
}
代码执行到 select 时,case 语句会按照源代码的顺序被评估,且只评估一次,评估的结果会出现下面这几种情况:
- 除 default 外,如果只有一个 case 语句评估通过,那么就执行这个case里的语句;
- 除 default 外,如果有多个 case 语句评估通过,那么通过伪随机的方式随机选一个;
- 如果 default 外的 case 语句都没有通过评估,那么执行 default 里的语句;
- 如果没有 default,那么 代码块会被阻塞,直到有一个 case 通过评估;否则一直阻塞
2.2. 随机性
func TestSelect6(t *testing.T) {
output1 := make(chan string)
output2 := make(chan string)
go server1(output1)
go server2(output2)
time.Sleep(time.Second)
select {
case s1 := <-output1:
fmt.Println(s1)
case s2 := <-output2:
fmt.Println(s2)
}
}
func server1(ch chan string) {
ch <- "from server1"
}
func server2(ch chan string) {
ch <- "from server2"
}
//输出
有时候输出
from server2
有时候输出
from server1
2.3. select{} 永远阻塞
func TestSelect2(t *testing.T) {
go func() {
fmt.Println("aaaaaaaaaaa")
}()
select {}
}
//输出
aaaaaaaaaaa
fatal error: all goroutines are asleep - deadlock!
2.4. nil channel永久阻塞
func TestSelect5(t *testing.T) {
var ch chan string
select {
case v := <-ch: //nil channel永久阻塞
fmt.Println("received value", v)
}
}
//输出
fatal error: all goroutines are asleep - deadlock!
2.5. 跳出for-select 循环
使用return或者goto
func TestSelect3(t *testing.T) {
ch1 := make(chan int)
ch2 := make(chan int)
go func() {
time.Sleep(time.Second * 5)
ch1 <- 1
}()
go func() {
time.Sleep(time.Second * 10)
ch2 <- 2
}()
for {
select {
case data := <-ch1:
fmt.Println(data)
break//break仅能退出select,不能退出for
case data := <-ch2:
fmt.Println(data)
goto TAG//可以使用goto或者return
}
fmt.Println("exit select")
}
TAG:
fmt.Println("exit for")
}
//输出
1
exit select
2
exit for
2.6. 为请求设置超时时间
func main() {
c := requestBaidu()
timeout := time.After(5 * time.Second)
for {
select {
case s := <-c:
fmt.Println(s)
case <-timeout:
fmt.Println("请求超时.")
return
}
}
}
func requestBaidu() chan int {
return make(chan int)
}
//输出
请求超时.
2.7. quit channel
func TestSelect4(t *testing.T) {
quit := make(chan int)
data := make(chan int)
go func() {
for {
select {
case d := <-data:
fmt.Println("收到数据:", d)
case <-quit:
fmt.Println("退出")
return
}
}
}()
for i := 0; i < 10; i++ {
data <- i
}
quit <- 3
time.Sleep(time.Minute)
}
3. 原理
3.1. select{}
func TestSelect(t *testing.T) {
select {}
}
- go tool compile -S select1_test.go
0x0000 00000 (select2_test.go:5) TEXT "".TestSelect(SB), ABIInternal, $8-8
0x0000 00000 (select2_test.go:5) MOVQ TLS, CX
0x0009 00009 (select2_test.go:5) MOVQ (CX)(TLS*2), CX
0x0010 00016 (select2_test.go:5) CMPQ SP, 16(CX)
0x0014 00020 (select2_test.go:5) JLS 40
0x0016 00022 (select2_test.go:5) SUBQ $8, SP
0x001a 00026 (select2_test.go:5) MOVQ BP, (SP)
0x001e 00030 (select2_test.go:5) LEAQ (SP), BP
0x0022 00034 (select2_test.go:5) FUNCDATA $0, gclocals·2a5305abe05176240e61b8620e19a815(SB)
0x0022 00034 (select2_test.go:5) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0022 00034 (select2_test.go:5) FUNCDATA $2, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0022 00034 (select2_test.go:6) PCDATA $0, $0
0x0022 00034 (select2_test.go:6) PCDATA $1, $0
0x0022 00034 (select2_test.go:6) CALL runtime.block(SB)
0x0027 00039 (select2_test.go:6) XCHGL AX, AX
0x0028 00040 (select2_test.go:6) NOP
0x0028 00040 (select2_test.go:5) PCDATA $1, $-1
0x0028 00040 (select2_test.go:5) PCDATA $0, $-1
0x0028 00040 (select2_test.go:5) CALL runtime.morestack_noctxt(SB)
0x002d 00045 (select2_test.go:5) JMP 0
可以看出调用的是CALL runtime.block(SB)
3.2. 一个case
select {
case v <- ch:
//...
}
//翻译为
if v <- ch {
//...
}
3.3. 一个case+default
select {
case v <- ch:
//...
default:
//...
}
//翻译为
if v <- ch {
//...
} else {
//...
}
3.4. 多个case
func TestSelect4(t *testing.T) {
quit := make(chan int)
data := make(chan int)
go func() {
for {
select {
case d := <-data:
fmt.Println("收到数据:", d)
case <-quit:
fmt.Println("退出")
return
}
}
}()
for i := 0; i < 10; i++ {
data <- i
}
quit <- 3
time.Sleep(time.Minute)
}
- go tool compile -S select1_test.go
0x0128 00296 (select1_test.go:15) PCDATA $0, $8
0x0128 00296 (select1_test.go:15) PCDATA $1, $0
0x0128 00296 (select1_test.go:15) LEAQ ""..autotmp_19+128(SP), DX
0x0130 00304 (select1_test.go:15) PCDATA $0, $0
0x0130 00304 (select1_test.go:15) MOVQ DX, (SP)
0x0134 00308 (select1_test.go:15) PCDATA $0, $8
0x0134 00308 (select1_test.go:15) LEAQ ""..autotmp_20+64(SP), DX
0x0139 00313 (select1_test.go:15) PCDATA $0, $0
0x0139 00313 (select1_test.go:15) MOVQ DX, 8(SP)
0x013e 00318 (select1_test.go:15) MOVQ $2, 16(SP)
0x0147 00327 (select1_test.go:15) CALL runtime.selectgo(SB)
0x014c 00332 (select1_test.go:15) MOVQ 24(SP), AX
调用的是runtime.selectgo(SB)