无缓冲通道不分配数据缓存空间,仅含锁、等待队列指针等元数据;发送时值直接从发送goroutine栈复制到接收goroutine栈,绕过堆内存。

无缓冲通道不是“内存中存数据的管道”,它根本不占用用户可感知的数据缓存空间——发送方和接收方的变量值直接在 goroutine 栈之间移交,不经过 channel 自身的缓冲区。
无缓冲通道的内存模型:没有缓冲区,只有同步点
很多人误以为 make(chan int) 会在堆上分配一块“用来存数据”的内存。实际上,Go 运行时只为通道结构体本身分配少量元数据(如锁、等待队列指针、类型信息),但**不为元素值分配任何缓冲存储空间**。所谓“发送”,本质是把发送 goroutine 栈上的值,通过调度器协调,直接复制到接收 goroutine 栈上(或其寄存器中);整个过程绕过了堆内存暂存。
- 发送操作
ch 不会把 <code>x拷贝进 channel 内部;它只是挂起当前 goroutine,并把x的地址/值传给运行时,等待接收方就绪 - 接收操作
就绪后,运行时直接将该值写入接收方变量所在栈帧,不经过中间缓冲 - 如果你用
pprof查看 heap profile,make(chan int)几乎不增加堆内存占用;而make(chan int, 1000)则会明显增加
为什么 ch 会阻塞?不是因为“满了”,而是因为没人收
阻塞不是源于内存不足或队列满,而是运行时检测到当前没有 goroutine 处于“等待从该 channel 接收”的状态。此时发送 goroutine 被标记为 waiting,并从运行队列移出——它不消耗 CPU,也不占用额外栈空间,只是在 channel 的 recvq 队列里挂个节点。
- 这个阻塞是协作式的:发送方让出执行权,调度器转去执行其他 goroutine
- 一旦有 goroutine 执行
,运行时立即将其唤醒,并完成值传递 - 若始终无人接收,该 goroutine 就永远卡在
ch ,最终导致整个程序死锁(Go runtime 会 panic “all goroutines are asleep”)
与有缓冲通道的内存行为对比
有缓冲通道(如 make(chan int, 10))会在堆上分配一个数组用于暂存元素,而无缓冲通道连这个数组都没有。这意味着:
- 无缓冲通道的创建开销极小(仅几十字节元数据),适合高频、轻量级同步信号
- 无法用
len(ch)获取“已存数据量”——对无缓冲通道,len永远返回 0;cap也永远是 0 - 不能用
select+default做非阻塞探测:select { case 中,无缓冲通道的 <code>case只有在对方正执行ch 时才可能就绪,否则立刻走 <code>default
真正要注意的是:无缓冲通道的“零内存暂存”特性,让它成为同步原语而非数据容器。一旦你开始想“往里面多塞几个值”,说明你其实需要的是有缓冲通道,或者该换用 sync.WaitGroup / sync.Once 等更明确的同步工具。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











