一下这段代码会打印出0到9吗?
func TestChannel(t *testing.T) {
c := make(chan int)
go func() {
for i := 0; i < 10; i++ {
select {
case c <- i:
default:
}
}
fmt.Println("put end")
}()
time.Sleep(time.Second)
for i := range c {
fmt.Println(i)
}
}
答案是不会打印出来。接下来看看channel和select实现,来解答为什么不会打印出来。
channel和select部分源码
channel结构定义
type hchan struct {
qcount uint // total data in the queue
dataqsiz uint // size of the circular queue
buf unsafe.Pointer // points to an array of dataqsiz elements
elemsize uint16
closed uint32
elemtype *_type // element type
sendx uint // send index
recvx uint // receive index
recvq waitq // list of recv waiters
sendq waitq // list of send waiters
// lock protects all fields in hchan, as well as several
// fields in sudogs blocked on this channel.
//
// Do not change another G's status while holding this lock
// (in particular, do not ready a G), as this can deadlock
// with stack shrinking.
lock mutex
}
selectgo源码截取
// pass 1 - look for something already waiting
var casi int
var cas *scase
var caseSuccess bool
var caseReleaseTime int64 = -1
var recvOK bool
for _, casei := range pollorder {
casi = int(casei)
cas = &scases[casi]
c = cas.c
if casi >= nsends {
sg = c.sendq.dequeue()
if sg != nil {
goto recv
}
if c.qcount > 0 {
goto bufrecv
}
if c.closed != 0 {
goto rclose
}
} else {
if raceenabled {
racereadpc(c.raceaddr(), casePC(casi), chansendpc)
}
if c.closed != 0 {
goto sclose
}
sg = c.recvq.dequeue()
if sg != nil {
goto send
}
if c.qcount < c.dataqsiz {
goto bufsend
}
}
}
// 上面的循环没有找到非阻塞可接受或发送数据的case,且非阻塞(即存在defaul),则跳到retc执行default 代码并退出
if !block {
selunlock(scases, lockorder)
casi = -1
goto retc
}
selectgo源码解读
if casi >= nsends{....} 这部分代码表示:当case是revc channel时
- 如果发送队列不为空,跳转到recv接收消息逻辑
- 如果channel buffer队列还有空位置,跳转到bufrecv逻辑
- 如果channel关闭,跳转到rclose逻辑
if casi >= nsends{....}else代码表示:当case是send channel时
- channel关闭,跳转到sclose逻辑
- 接收队列不为空,跳转到send逻辑
- buffer有空位,跳转到bugsend逻辑
看过channe源码的同学应该知道,sendq用于记录等待发送的goroutine。当我们使用 select default 跳过阻塞时,当前的这个goroutine并没有加入到sendq队列中。所以当我们第二段代码从channel读数据时,并没有任何发送goroutine在队列中。
总结
当select走default逻辑时,并不会把当前goroutine加入到其他case中的channel收发队列中。