稀土掘金 稀土掘金

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

chan1
main exit
chan2
main exit
default
main 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 也可以读取

资讯网陈乔恩整容iostf签名娜的专属昵称起名字女孩大全恩何姓男孩起名大全2021最新版在哪个网站找房子设计一八年男孩起名名字测试分周易起企业名称大全动漫起名字广州网站建设费用高级珠宝首饰周易六十四卦用途肥城网站设计雷姓起名起名大全姓肖什么网站设计比较好网站关键优化姓邱女孩的女孩起什么名字好祛痘膏起什么名字好SEO站群软件商丘永城职业学校家具公司起名的网站设计难做吗应城市地图起名测名用什麽软件好深圳网站seo优化排名关于寝室的鬼故事在线听徐姓起名女孩姓名大全描写人物外貌的成语少年生前被连续抽血16次?多部门介入两大学生合买彩票中奖一人不认账让美丽中国“从细节出发”淀粉肠小王子日销售额涨超10倍高中生被打伤下体休学 邯郸通报单亲妈妈陷入热恋 14岁儿子报警何赛飞追着代拍打雅江山火三名扑火人员牺牲系谣言张家界的山上“长”满了韩国人?男孩8年未见母亲被告知被遗忘中国拥有亿元资产的家庭达13.3万户19岁小伙救下5人后溺亡 多方发声315晚会后胖东来又人满为患了张立群任西安交通大学校长“重生之我在北大当嫡校长”男子被猫抓伤后确诊“猫抓病”测试车高速逃费 小米:已补缴周杰伦一审败诉网易网友洛杉矶偶遇贾玲今日春分倪萍分享减重40斤方法七年后宇文玥被薅头发捞上岸许家印被限制高消费萧美琴窜访捷克 外交部回应联合利华开始重组专访95后高颜值猪保姆胖东来员工每周单休无小长假男子被流浪猫绊倒 投喂者赔24万小米汽车超级工厂正式揭幕黑马情侣提车了西双版纳热带植物园回应蜉蝣大爆发当地回应沈阳致3死车祸车主疑毒驾恒大被罚41.75亿到底怎么缴妈妈回应孩子在校撞护栏坠楼外国人感慨凌晨的中国很安全杨倩无缘巴黎奥运校方回应护栏损坏小学生课间坠楼房客欠租失踪 房东直发愁专家建议不必谈骨泥色变王树国卸任西安交大校长 师生送别手机成瘾是影响睡眠质量重要因素国产伟哥去年销售近13亿阿根廷将发行1万与2万面值的纸币兔狲“狲大娘”因病死亡遭遇山火的松茸之乡“开封王婆”爆火:促成四五十对奥巴马现身唐宁街 黑色着装引猜测考生莫言也上北大硕士复试名单了德国打算提及普京时仅用姓名天水麻辣烫把捣辣椒大爷累坏了

资讯网 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化