20-chan原理4-select

6. select

select语句的语法:

  1. 每个case都必须是一个channel的发送、或者接收操作。
  2. 所有channel表达式都会被求值。
  3. 如果任意case中表达式可以进行,它就执行,其他被忽略。
  4. 如果有多个 case都可以运行,Select会随机公平地选出一个执行。其他不会执行。
  5. 一个没有任何caseselect语句写作select{},会永远地等待下去
  6. 没有break
    否则:
  7. 如果有 default子句,则执行该语句。
  8. 如果没有 default子句,select阻塞,直到某个通信可以运行;Go 不会重新对 channel 或值进行求值。

6.1 与for 组合

for内部存在select时,break不能跳出for循环。需要用break到标签,或者goto到标签。

break 标签

for {
	select {
	case ...:
			break ForEnd
	}
	fmt.Println("inside the for: ")
}
ForEnd:

goto 标签

for {
	select {
	case ...:
			goto ForEnd
	}
	fmt.Println("inside the for: ")
}
ForEnd:

6.2 scase

src\runtime\select.go
// 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
	kind        uint16
	pc          uintptr // race pc (for race detector / msan)
	releasetime int64
}

6.3 selectgo

select表达式的底层实现。
select表达式中的case、default被转换为scase数组。
src\runtime\select.go


// selectgo implements the select statement.
//
// cas0 points to an array of type [ncases]scase, and order0 points to
// an array of type [2*ncases]uint16. Both reside on the 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.
// Also, if the chosen scase was a receive operation, it reports whether
// a value was received.
func selectgo(cas0 *scase, order0 *uint16, ncases int) (int, bool) {

6.4 selectgo执行逻辑

  1. 打乱所有case对应的scase结构体顺序。fastrandn
  2. 对所有channel加锁。
    sellock(scases, lockorder)
  3. 遍历所有channel,判断是否可读,或者可写。
    3.1 如果有case可读,读取channel中数据,解锁所有channel,返回对应scase,true。
    recv(c, sg, cas.elem, func() { selunlock(scases, lockorder) }, 2)
    3.2 如果case可写,将数据写入channel,解锁所有channel,返回对应scase,false
    3.3 没有case可读写,但是有default,解锁所有channel,返回default对应的scasefalse。
  4. 没有case可读写,没有default
    4.1 将当前协程加入到channel等待队列。
    4.2 将协程转入阻塞,等待被唤醒。
    gopark(selparkcommit, nil, waitReasonSelect, traceEvGoBlockSelect, 1)
  5. 唤醒后
    5.1 解锁所有channel
    5.2 找到对应scase
    5.3 如果是可读,解锁所有channel,返回对应scase,true
    5.4 如果是可写,解锁所有channel,返回对应scase,false

pass 2 - enqueue on all chans
gp = getg()
// wait for someone to wake us up
解锁所有channel
sellock(scases, lockorder)

// pass 3 - dequeue from unsuccessful chans

你可能感兴趣的:(Go,实现原理,chan,源码分析)