前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go高阶指南09,select 实现原理

Go高阶指南09,select 实现原理

作者头像
微客鸟窝
发布2021-09-10 15:38:36
7080
发布2021-09-10 15:38:36
举报
文章被收录于专栏:Go语言指北Go语言指北

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

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

答题环节

  1. 下面程序输出什么?
代码语言:javascript
复制
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 语句并退出。因此,次程序有可能产生三种输出:

代码语言:javascript
复制
chan1
main exit
代码语言:javascript
复制
chan2
main exit
代码语言:javascript
复制
default
main exit
  1. 下面程序输出什么?
代码语言:javascript
复制
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. 下面程序输出什么?
代码语言:javascript
复制
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. 下面程序输出什么?
代码语言:javascript
复制
package main

func main() {
  select {
  }
}

答案:对于空的 select 语句,程序会被阻塞,确切的说是当前协程被阻塞,同时 Go 自带死锁检测机制,当发现当前协程再也没有机会被唤醒时,则会发生 panic。所以上述程序会 panic。

实现原理

Go 实现 select 时,定义了一个数据结构表示每个 case 语句(包含defaut),select 执行过程可以类比成一个函数,函数输入 case 数组,输出选中的 case,然后程序流程转到选中的 case块。

源码包 src/runtime/select.go 定义了表示case语句的数据结构:

代码语言:javascript
复制
// 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 也可以读取

有什么问题,可以公众号内回复或加我微信交流。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-08-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 微客鸟窝 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 答题环节
  • 实现原理
  • 归纳总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档