网上对于channel的解析有很多,我就分享一下个人理解。感觉channel类似 linux 里面管道,IO流的概念。它有输入和输出的概念,同时会发生阻塞。
无缓存的channel类型下面的图片,输入和输出直接经过channel。
func main() {
ch := make(chan struct{}) // 无缓冲区的channel
go func() {
ch <- struct{}{} // 协程 阻塞挂起 直到有接收方
}()
for {
fmt.Println(runtime.NumGoroutine())
time.Sleep(time.Second)
}
}
有缓冲区的channel,类似下面的这个图,拥有容量有线的环形元素数组和长度不限制的存放阻塞数据的队列。
有几个需要注意的点
- channel没有流出,也就是接收方。会阻塞挂起,(在执行)
- 拥有缓冲区的channel,缓冲区可以接收数据,不超容量不会阻塞
我们可以从channel的数据结构体上印证一下
type hchan struct {
qcount uint // 当前 channel 中存在多少个元素;
dataqsiz uint // size of the circular queue
buf unsafe.Pointer // channel 中用于存放元素的环形缓冲区;
elemsize uint16
closed uint32
elemtype *_type // element type
sendx uint // send index
recvx uint // receive index
recvq waitq // 因接收而陷入阻塞的协程队列;
sendq waitq // 因发送而陷入阻塞的协程队列;
lock mutex // 锁
}
关于阻塞
发送和接收channel超出容量,都会陷入阻塞。看一下怎么发送的阻塞。主要就是 gopark的调用。
gopark 将 g 变为阻塞态 waiting (g与m解绑, g等待新一轮的调度)
// No stack splits between assigning elem and enqueuing mysg
// on gp.waiting where copystack can find it.
mysg.elem = ep
mysg.waitlink = nil
mysg.g = gp
mysg.isSelect = false
mysg.c = c
gp.waiting = mysg
gp.param = nil
c.sendq.enqueue(mysg)
// Signal to anyone trying to shrink our stack that we're about
// to park on a channel. The window between when this G's status
// changes and when we set gp.activeStackChans is not safe for
// stack shrinking.
atomic.Store8(&gp.parkingOnChan, 1)
gopark(chanparkcommit, unsafe.Pointer(&c.lock), waitReasonChanSend, traceEvGoBlockSend, 2)
// Ensure the value being sent is kept alive until the
// receiver copies it out. The sudog has a pointer to the
// stack object, but sudogs aren't considered as roots of the
// stack tracer.
KeepAlive(ep)
// someone woke us up.
if mysg != gp.waiting {
throw("G waiting list is corrupted")
}
gp.waiting = nil
gp.activeStackChans = false
closed := !mysg.success
gp.param = nil
if mysg.releasetime > 0 {
blockevent(mysg.releasetime-t0, 2)
}
什么时候再变成可调度,我们看一下源码的 send channel 位置。会有调用 goready,将g由waiting阻塞态变为可调度,等待被调度。走gmp调度处理。
select为什么能监听channel
如果go协程已经陷入阻塞状态了,那么select为什么还能监听到channel呢?如果有select监听的时候,会进入一个类似自旋的状态,没有真正的阻塞。非阻塞模式。
默认情况下,读/写 channel 都是阻塞模式,只有在 select 语句组成的多路复用分支中,与 channel 的交互会变成非阻塞模式:
以上就是我关于channel的理解。文字描述有限,后面会有一次视频的描述。