- 下面程序输出什么?
package main import ( "fmt" "time" ) func main() { chan1 := make(chan int) chan2 := make(chan int) go func() { chan1 <- 1 time.Sleep(5 * time.Second) }() go func() { chan2 <- 1 time.Sleep(5 * time.Second) }() select { case <- chan1: fmt.Println("chan1") case <- chan2: fmt.Println("chan2") default: fmt.Println("default") } fmt.Println("main exit") } 复制代码
答案:
select 中的 case 执行顺序是随机的,如果某个 case 中的 channel 已经 ready,那么就会执行相应的语句并退 出 select 流程,如果所有 case 中的 channel 都未 ready,那么就会执行 default 中的语句然后退出 select 流程。
由于启动的协程和 select 语句并不能保证执行的顺序,所以也有可能 select 执行时协程还未向channel中写入数据,所以 select 直接执行 default 语句并退出。因此,次程序有可能产生三种输出:
chan1 main exit 复制代码
chan2 main exit 复制代码
default main exit 复制代码
- 下面程序输出什么?
package main import ( "fmt" "time" ) func main() { chan1 := make(chan int) chan2 := make(chan int) writeFlag := false go func() { for { if writeFlag { chan1 <- 1 } time.Sleep(5 * time.Second) } }() go func() { for { if writeFlag { chan2 <- 1 } time.Sleep(5 * time.Second) } }() select { case <- chan1: fmt.Println("chan1") case <- chan2: fmt.Println("chan2") } fmt.Println("main exit.") } 复制代码
答案:
和第一题一样,select 会随机检测各 case 语句中 channel是否 ready,如果有 case 中 channel 已经 ready 则执行相应的 case 语句后退出 select 流程,如果所有的 channel 都未 ready 且没有 default 的话,则会阻 塞等待各个 channel。因此上述程序会一直阻塞。
- 下面程序输出什么?
package main import ( "fmt" ) func main() { chan1 := make(chan int) chan2 := make(chan int) go func() { close(chan1) }() go func() { close(chan2) }() select { case <- chan1: fmt.Println("chan1") case <- chan2: fmt.Println("chan2") } fmt.Println("main exit.") } 复制代码
答案:
select 会随机检测各 case 语句中 channel 是否 ready,注意已关闭的 channel 也是可读的,所以上述程序中select 不会阻塞,具体执行哪个 case 语句具是随机的。
- 下面程序输出什么?
package main func main() { select { } } 复制代码
答案: 对于空的 select 语句,程序会被阻塞,确切的说是当前协程被阻塞,同时 Go 自带死锁检测机制,当发现当前协程再也没有机会被唤醒时,则会发生 panic。所以上述程序会 panic。
实现原理
Go 实现 select 时,定义了一个数据结构表示每个 case 语句(包含defaut),select 执行过程可以类比成一个函数,函数输入 case 数组,输出选中的 case,然后程序流程转到选中的 case块。
源码包 src/runtime/select.go
定义了表示case语句的数据结构:
// Select case descriptor. // Known to compiler. // Changes here must also be made in src/cmd/internal/gc/select.go's scasetype. type scase struct { c *hchan // chan elem unsafe.Pointer // data element } 复制代码
- c : 表示当前 case 语句所操作的 channel 指针
- elem :表示缓冲区地址
归纳总结
- select 语句中除 default 外,每个 case 操作一个channel,要么读要么写
- select语句中除 default 外,各 case 执行顺序是随机的
- select 语句中如果没有 default 语句,则会阻塞等待任一 case
- select 语句中读操作要判断是否成功读取,关闭的 channel 也可以读取