channel底层数据结构
1 | type hchan struct { |
其中需要注意的是:
-
buf
指向底层循环数组,如果是非缓冲则指向hchan地址 -
sendq
,recvq
分别表示被阻塞的 goroutine,这些 goroutine 由于尝试读取 channel 或向 channel 发送数据而被阻塞(部分通过直接复制memmove的方式传输未被阻塞,也就不在链表中) -
waitq
是sudog
的一个双向链表,而sudog
实际上是对 goroutine 的一个封装1
2
3
4type waitq struct {
first *sudog
last *sudog
} -
lock
用来保证每个读 channel 或写 channel 的操作都是原子的

chan初创建
chan的创建分为含有缓冲区和非缓冲区,都通过 func makechan(t *chantype, size int64) *hchan
实现
1 | func makechan64(t *chantype, size int64) *hchan { |
以上有几点注意:
- 针对创建不同的chan初始化过程
- 非缓冲chan 只需要分配一个hchanSize
- 缓冲区chan 不含有指针,则分配一个连续的内存
- 缓冲区chan 含有指针,分别分配hchan和 指针需要的内存大小
- chan 分配在堆中,返回一个指针*hchan
- sendq 和 recvq 链表由goroutine初始化时候创建,不在这里创建
chan接收
接收的操作有两种写法
1 | // entry points for <- c from compiled code |
received
反应channel是否被关闭- 接收值会放到
elem
所指向的指针,如果忽略接收值,则elem
为nil - 第三个参数 都使用 true 表示阻塞模式,
1 | /* |
需要注意的是
- 同一个 chan 不能被重新开启
- 在缓冲区chan buf满了的情况下,发送协程阻塞,则接收者还是会优先处理缓冲区数据
1 | //如果是非缓冲型的,就直接从发送者的栈拷贝到接收者的栈 |

chan发送
ch <- 3
最终会转换成 chansend
函数
1 | func full(c *hchan) bool { |
需要注意的是:
- KeepAlive 的作用和原理是?
1 | func send(c *hchan, sg *sudog, ep unsafe.Pointer, unlockf func(), skip int) { |
关闭chan
1 | func closechan(c *hchan) { |
chan应用
- 针对不同的chan会有不同的效果:
-
控制并发数
1
2
3
4
5
6
7
8
9
10
11
12
13var limit = make(chan int, 3)
func main() {
// …………
for _, w := range work {
go func() {
limit <- 1
w()
<-limit
}()
}
// …………
}