特性
select是Go中的一个控制结构,类似于switch语句,用于处理异步IO操作。
go select 的特性:
1.case语句必须是一个channel操作。
2.select中的default子句总是可运行的。
3.所有channel表达式都会被求值
4.所有被发送的表达式都会被求值
5.如果没有可运行的case语句,且没有default语句,select将阻塞,直到某个case通信可以运行
6.如果有多个case都可以运行,select会随机公平地选出一个执行。其他不会执行。否则执行default子句
type scase struct {
c *hchan // chan
elem unsafe.Pointer // data element
}
c为当前case语句所操作的channel指针
elem表示要读写的channel的数据存放地址
实现原理
func selectgo(cas0 *scase, order0 *uint16, pc0 *uintptr, nsends, nrecvs int, block bool) (int, bool) {
//1. 锁定scase语句中所有的channel
//2. 按照随机顺序检测scase中的channel是否ready
// 2.1 如果case可读,则读取channel中数据,解锁所有的channel,然后返回(case index, true)
// 2.2 如果case可写,则将数据写入channel,解锁所有的channel,然后返回(case index, false)
// 2.3 所有case都未ready,则解锁所有的channel,然后返回(default index, false)
//3. 所有case都未ready,且没有default语句
// 3.1 将当前协程加入到所有channel的等待队列
// 3.2 当将协程转入阻塞,等待被唤醒
//4. 唤醒后返回channel对应的case index
// 4.1 如果是读操作,解锁所有的channel,然后返回(case index, true)
// 4.2 如果是写操作,解锁所有的channel,然后返回(case index, false)
}
例子
func main() {
c := make(chan bool, 1)
close(c)
for {
select {
case <- time.After(time.Second):
fmt.Println("hello")
case _, ok := <- c:
if !ok {
fmt.Println("done")
return
}
}
}
}
这里输出结果是done, 因为即使channel关闭了,还是能读取并返回对应元素类型的零值。
func main() {
c := make(chan bool, 1)
close(c)
for {
select {
case <- time.After(time.Second):
fmt.Println("hello")
case _, ok := <- c:
if !ok {
fmt.Println("done")
return
}
}
}
}
func main() {
c := make(chan bool, 1)
close(c)
for {
select {
case <- time.After(time.Second):
fmt.Println("hello")
case <- c:
}
}
}
什么都不会输出,因为每次 select 都会执行到 case <- c,select 再次对 case1 的 timer.After 求值,返回一个新的下一秒超时的 Timer。
func main() {
select {
}
}
什么都不会输出,会panic,Golang自带死锁检测机制,当发现当前协程再也没有机会被唤醒时,则会panic。