channel 结构体
- type hchan struct {
- qcount uint // 大小
- dataqsiz uint // 有缓存的队列大小
- buf unsafe.Pointer // 有缓存的循环队列指针
- elemsize uint16
- closed uint32
- elemtype *_type // 类型
- sendx uint // 有缓存的可发送下标
- recvx uint // 有缓存的可存储下标
- recvq waitq // 接受的 goroutine 抽象出来的结构体 sudog 的队列, 是一个双向链表
- sendq waitq // 同上, 是发送的相关链表
- lock mutex // 互斥锁
- }
channel 创建
ch := make(chan int,3)
创建 channel 实际上就是在内存中实例化了一个 hchan 的结构体, 并返回一个 ch 指针, 我们使用过程中 channel 在函数之间的传递都是用的这个指针, 这就是为什么函数传递中无需使用 channel 的指针, 而直接用 channel 就行了, 因为 channel 本身就是一个指针.
channel 发送和接收
go 中的经典话语 Do not communicate by sharing memory; instead, share memory by communicating.
说的事不要通过共享内存进行通信, 而应该通过通信达到共享数据的目的
缓存未满或未空的情况
channel 中的发送盒接收可以细化为下面三个步骤
加锁
把数据从 goroutine 中 copy 到队列 (或者队列中 copy 到 goroutine)
释放锁
缓存满或空的情况
情况一
当 G1 已经将 channel 的缓存存满后, 当再次进行 send 操作 ch<-1 的时候, 会主动调用 Go 的调度器, 让 G1 等待, 并从让出 M, 让其他 G 去使用, 同时 G1 也会被抽象成含有 G1 指针和 send 元素的 sudog 结构体保存到 hchan 的 sendq 中等待被唤醒.
当 G2 执行了 recv 操作 p := <-ch, 于是 G2 从缓存队列中取出数据, channel 会将等待队列中的 G1 推出, 将 G1 当时 send 的数据推到缓存中, 然后调用 Go 的 scheduler, 唤醒 G1, 并把 G1 放到可运行的 Goroutine 队列中.
情况二
当 G2 在 channel 的缓存空时, 进行取操作, 则 G2 会主动调用 Go 的调度器, 让 G2 等待, 并从让出 M, 让其他 G 去使用. G2 还会被抽象成含有 G2 指针和 recv 空元素的 sudog 结构体保存到 hchan 的 recvq 中等待被唤醒
当 G1 开始向 channel 中推送数据 ch <- 1 时, 则 G1 并不会锁住 channel, 然后将数据放到缓存中, 而是直接把数据从 G1 直接 copy 到了 G2 的栈中. 这种方式在唤醒过程中, G2 无需再获得 channel 的锁, 然后从缓存中取数据. 减少了内存的 copy, 提高了效率.
参考
图解 Go 的 channel 底层原理 https://www.cnblogs.com/RyuGou/p/10776565.html
Go channel 实现原理分析
来源: http://www.bubuko.com/infodetail-3462703.html