Channel

底层数据结构
type hchan struct {
	qcount   uint           // 当前队列中剩余元素个数
	dataqsiz uint           // 环形队列长度,即可以存放的元素个数
	buf      unsafe.Pointer // 环形队列指针
	elemsize uint16         // 每个元素的大小
	closed   uint32	        // 标识关闭状态
	elemtype *_type         // 元素类型
	sendx    uint           // 队列下标,指示元素写入时存放到队列中的位置
	recvx    uint           // 队列下标,指示元素从队列的该位置读出
	recvq    waitq          // 等待读消息的goroutine队列
	sendq    waitq          // 等待写消息的goroutine队列
	lock mutex              // 互斥锁,chan不允许并发读写
}
waitqsudog
1. type waitq struct {
2.    first *sudog
3.    last  *sudog
4. }
sudog

存储了两个分别指向前后sudog的指针用来构成链表

发送数据
  • 如果当前channel的recvq上存在已经被阻塞的Goroutine(也就是说有goroutine在等待读消息),那么会直接将数据发送给当前的Goroutine并将其设置成下一个运行的Goroutine(设置处理器runnext属性,不会立刻调度)
  • 如果channel存在缓冲区并且还有空余位置,会直接将数据存储到缓存区sendx所在的位置上
  • 如果不满足上述两种情况,会创建一个sudog结构并将其加入channel的sendq队列中,当前Goroutine陷入阻塞等待其他协程从Channel接收数据
接收数据
runtime.sudog
关闭通道

当 Channel 是一个空指针或者已经被关闭时,Go 语言运行时都会直接崩溃并抛出异常

处理完了这些异常的情况之后就可以开始执行关闭 Channel 的逻辑了,close 函数先上一把大锁,接着把所有挂在这个 channel 上的 sender 和 receiver 全都连成一个 sudog 链表,再解锁。最后,再将所有的 sudog 全都唤醒。

唤醒之后,sender 会检测到channel已经关闭,panic。从一个有缓冲的 channel 里读数据,当 channel 被关闭,依然能读出有效值。只有当返回的 ok 为 false 时,读出的数据才是无效的,为对应类型的零值。

x, ok := <-ch
产生panic的情况

向一个关闭的 channel 进行写操作;关闭一个 nil 的 channel;重复关闭一个 channel。

总结

channel缓存区是由循环队列实现的

channel的等待队列是一个双向链表

channel 的发送和接收操作本质上都是 “值的拷贝”

从一个有缓冲的 channel 里读数据,当 channel 被关闭,依然能读出有效值。发数据会panic。