select为golang提供了channel的多路IO复用机制,和其他IO多路复用一样,用于检测是否有读写事件就绪。
golang实现select的时候,实际为每一个case语句定义了一个数据结构,select语句块执行的时候,实际上可以类比成一个case数组处理的代码块(或者函数),然后程序流程转到选中的case块。
源码包src/runtime/select.go:scase定义了表示case语句的数据结构:
type scase struct {
c *hchan
kind uint16
elem unsafe.Pointer
}
scase.c表示当前case语句操作的chan指针,这也表明一个case只能监听一个chan。
scase.kind表示当前的chan是可读还是可写channel或者是default。三种类型分别由常量定义:
-
caseRecv:case语句中尝试读取scase.c中的数据;
-
caseSend:case语句中尝试向scase.c中写入数据;
-
caseDefault: default语句
scase.elem表示缓冲区地址,跟据scase.kind不同,有不同的用途:
-
scase.kind == caseRecv : scase.elem表示读出channel的数据存放地址;
-
scase.kind == caseSend : scase.elem表示将要写入channel的数据存放地址;
源码包src/runtime/select.go:selectgo()定义了select选择case的函数
// selectgo implements the select statement.
//
// *sel is on the current goroutine's stack (regardless of any
// escaping in selectgo).
//
// selectgo returns the index of the chosen scase, which matches the
// ordinal position of its respective select{recv,send,default} call.
func selectgo(sel *hselect) int {<br>
}
其中数据结构hselect如下:
// Select statement header.
// Known to compiler.
// Changes here must also be made in src/cmd/internal/gc/select.go's selecttype.
type hselect struct {
tcase uint16 // total count of scase[]
ncase uint16 // currently filled scase[]
pollorder *uint16 // case poll order
lockorder *uint16 // channel lock order
scase [1]scase // one per case (in order of appearance)
}
-
hselect.tcase存的是scase总数。
-
hselect.pollorder是保存scase的随机后的序列。以达到随机检测case的目的。
-
hselect.lockorder是保存的channel地址。所有case语句中channel序列,以达到去重防止对channel加锁时重复加锁的目的。
-
selectgo返回int,表示选中的scase,也就是ready的channel index。
该函数执行逻辑大致如下:
-
锁定scase语句中所有的channel
-
按照随机顺序检测scase中的channel是否ready
-
如果case可读,则读取channel中数据,解锁所有的channel,然后返回(case index)
-
如果case可写,则将数据写入channel,解锁所有的channel,然后返回(case index)
-
所有case都未ready,则解锁所有的channel,然后返回(default index)
- 所有case都未ready,且没有default语句
-
将当前协程加入到所有channel的等待队列
-
当将协程转入阻塞,等待被唤醒
- 唤醒后返回channel对应的case index
-
如果是读操作,解锁所有的channel,然后返回(case index)
-
如果是写操作,解锁所有的channel,然后返回(case index)