写点什么

Go 语言,深入了解 select 实现原理

用户头像
微客鸟窝
关注
发布于: 2 小时前
Go语言,深入了解 select 实现原理

select 是 GO 语言中用来提供 IO 复用的机制,它可以检测多个 chan 是否 ready(可读/可写)。


老规矩,我们先来答几道题试试水。

答题环节

  1. 下面程序输出什么?


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 语句并退出。因此,次程序有可能产生三种输出:


chan1main exit
复制代码


chan2main exit
复制代码


defaultmain exit
复制代码


  1. 下面程序输出什么?


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。因此上述程序会一直阻塞。


  1. 下面程序输出什么?


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 语句具是随机的。


  1. 下面程序输出什么?


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 也可以读取

发布于: 2 小时前阅读数: 5
用户头像

微客鸟窝

关注

还未添加个人签名 2019.11.01 加入

公众号《微客鸟窝》笔者,目前从事web后端开发,涉及语言PHP、golang。获得美国《时代周刊》2006年度风云人物!

评论

发布
暂无评论
Go语言,深入了解 select 实现原理