特性

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。