make(chan t, n) 编译为 runtime.makechan 调用,源码在 src/runtime/chan.go;size 赋给 dataqsiz,为 0 时 buf 为 nil,>0 时按 dataqsiz * elemsize 分配环形队列。

从 make(chan T, N) 开始定位 runtime 源码入口
Go 编译器把 make(chan T, N) 编译为对 runtime.makechan 的调用,不是 reflect.makechan —— 后者只是通过 //go:linkname 做了符号绑定,实际入口始终是 runtime.makechan。源码在 src/runtime/chan.go,函数签名是 func makechan(t *chantype, size int) *hchan。
关键点在于:参数 size 直接赋给 hchan.dataqsiz;若为 0,则 buf 保持 nil,不分配内存;若 > 0,则按 dataqsiz * elemsize 一次性 malloc 底层环形队列。
-
dataqsiz == 0→ 无缓冲 channel,buf == nil,所有收发走同步路径 - 大
dataqsiz+ 大elemsize(如[1024]byte)→buf分配巨大,可能触发栈溢出或 GC 频繁 -
make(chan struct{}, N)虽然元素大小为 0,但 runtime 仍会分配N * 0 = 0字节的buf,此时buf是合法的空指针,不 panic
ch 和 <code> 在底层如何判断是否阻塞
核心逻辑在 chansend 和 chanrecv 函数里,不依赖“尝试非阻塞”语义——Go channel 没有类似 try_send 的原生接口。是否阻塞,只看三件事:qcount、sendq 是否有等待接收者、recvq 是否有等待发送者。
- 写入时:
qcount 且 <code>recvq.first == nil→ 拷贝进buf[sendx],更新sendx和qcount - 写入时:
recvq.first != nil→ 跳过buf,直接把v拷贝到等待 goroutine 的sudog.elem,唤醒它 - 读取时:
qcount > 0→ 从buf[recvx]拷贝,更新recvx和qcount - 读取时:
qcount == 0且sendq.first != nil→ 直接从 sender 的sudog.elem拷贝,不碰buf
常见误判是以为 “缓冲区未满就一定能写入”,其实只要对面有 goroutine 正在 阻塞,就会立刻直传,<code>buf 完全被绕过。
关闭 channel 后的收发行为为什么不能靠 len(ch) == 0 判断是否可读
len(ch) 返回的是 hchan.qcount,即当前缓冲队列中剩余元素数;cap(ch) 返回的是 hchan.dataqsiz。两者都不反映 channel 是否已关闭。
- close 后:
hchan.closed = 1,但qcount不变,buf不清空 - recv 时:
qcount > 0→ 正常取值,返回true;qcount == 0→ 返回零值 +false - send 时:只要
closed == 1,立即 panic"send on closed channel" - 并发 close 同一个 channel → panic,Go 不做保护,需上层自己加锁或用
sync.Once
最易忽略的一点:接收方无法仅凭一次 返回 <code>false 就断定“数据已全部读完”,因为可能还有 goroutine 正在往未关闭的 channel 发送,而你还没收到——关闭动作必须由**唯一发送方**发起,并配合明确的信号机制(如额外的 done channel)。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











