无缓冲 channel 在发送或接收时若对方未就绪则一定会阻塞;其本质是同步通信管道,要求发送与接收双方同时就绪才能完成操作。

无缓冲 channel 什么时候一定会阻塞?
无缓冲 channel 的本质是同步通信管道:发送和接收必须「碰头」才能完成。只要一方没就绪,另一方立刻阻塞。
-
ch := make(chan int)创建后,ch 立即阻塞——因为没有 goroutine 在等接收 val := 同样立即阻塞——因为没人往里发数据,且 channel 未关闭- 哪怕只差毫秒,比如先启 goroutine 再发数据,但没加
time.Sleep或同步机制,仍可能因调度顺序导致主 goroutine 先执行发送而死锁
典型死锁报错:fatal error: all goroutines are asleep - deadlock! ——这不是 panic,是运行时直接终止,无法 recover。
有缓冲 channel 的阻塞边界在哪?
缓冲区像个小仓库,阻塞只发生在「满」或「空」的临界点,不是一写就卡。
- 创建
ch := make(chan int, 2)后,前两次ch 都不阻塞;第三次才阻塞,直到有人拿走至少一个值 - 接收方
只在缓冲区为空时阻塞;哪怕刚发过 100 个,只要被消费光了,下一次接收就停住 - 注意:缓冲区大小为 0 等价于无缓冲(
make(chan int, 0)),不是“非阻塞”,这点常被误读
ch := make(chan int, 1)
ch <- 1 // OK:存进缓冲区
ch <- 2 // 阻塞:缓冲区已满
go func() { <-ch }() // 另起 goroutine 消费,释放空间后 ch <- 2 才继续select 语句里的阻塞陷阱
select 本身不阻塞,但它所有 case 都不可达时,就会整体挂起——这是最隐蔽的阻塞来源之一。
立即学习“go语言免费学习笔记(深入)”;
- 所有 channel 都空(接收 case)、都满(发送 case),且没写
default→ 整个 goroutine 卡死 - 写了
default就是非阻塞轮询,但要注意:它不表示“失败”,而是“此刻不可行”,需自行判断是否重试或放弃 -
select中混用超时(time.After)和 channel 操作,是避免无限等待的标准做法
select {
case msg := <-ch:
fmt.Println("got", msg)
case <-time.After(1 * time.Second):
fmt.Println("timeout")
}nil channel 的特殊阻塞行为
未初始化的 channel 是 nil,它的收发操作不是报错,而是永久阻塞——连死锁错误都不会触发,goroutine 彻底“蒸发”。
-
var ch chan int声明后未make,直接ch 或→ 永久休眠 - 这种阻塞不会 panic,也不会被
select的default捕获,调试时极难定位 - 常见于条件分支中 channel 初始化遗漏,或函数参数传入了零值 channel
最容易被忽略的一点:channel 阻塞不是 bug,而是 Go 并发模型的设计契约;真正的问题,往往出在「谁该负责接收」「缓冲区是否匹配吞吐节奏」「有没有漏掉关闭或超时」——这些才是实际项目里反复踩坑的地方。










