无缓冲 channel 是同步通知的唯一可靠方式,其发送与接收必须同时就绪才能完成,底层由 go 运行时保证 goroutine 同步挂起与唤醒,适用于任务完成、初始化就绪等场景。

无缓冲 channel 是同步通知的唯一可靠方式
无缓冲 channel(make(chan int) 或 make(chan int, 0))天然强制收发双方“碰头”:发送方必须等到接收方就绪才返回,反之亦然。它不是“模拟同步”,而是 Go 运行时级的同步原语,底层直接挂起 goroutine 并等待对方唤醒。
常见误用是拿有缓冲 channel(比如 make(chan int, 1))当同步用——只要缓冲未满,发送就不会阻塞,通知可能被吞掉或延迟,根本无法保证“此时对方已收到”。
- 适用场景:任务完成信号、初始化就绪通知、错误中断传递(如
done := make(chan struct{})) - 关键约束:必须确保至少有一个接收者在监听,否则发送操作永久阻塞
- 典型错误:
ch := make(chan bool, 1); ch
有缓冲 channel 实现异步解耦的关键是容量与 close 配合
有缓冲 channel 的本质是生产者-消费者间的“暂存区”,解耦力度取决于缓冲大小和使用节奏。但缓冲 ≠ 异步安全:若只写不读、不 close、也不做超时控制,极易堆积 goroutine 或阻塞主流程。
真正可靠的异步模式,需要明确谁负责关闭、谁负责消费完毕、以及如何防止漏处理。
- 缓冲大小选 1 通常不够:网络请求、批量处理等场景易溢出;建议根据峰值并发或业务 SLA 设定(如
make(chan *Request, 100)) - 必须由生产者或协调者调用
close(ch),否则消费者range ch永不退出,会永远阻塞 - 避免仅靠
len(ch) == cap(ch)判断是否满——这是瞬时快照,竞争下不可靠;应依赖 select + default 或带 timeout 的 send
select + default 是防止 goroutine 卡死的保底手段
无论是同步通知还是异步解耦,一旦 channel 操作没有必然成功的保障(比如接收方可能退出、网络超时、上下文取消),裸写 ch 或 <code> 就等于把命运交给调度器——很可能卡住整个 goroutine。
select 加 default 或 timeout 才是真实工程中的防御性写法。
- 非阻塞发送:
select { case ch - 带超时接收:
select { case v := - 注意:
default分支会让操作变成“尽力而为”,需确认业务能否容忍丢失或跳过
同步与异步混用时最容易忽略的 close 时机
混合模式常见于“主流程启动 worker,用无缓冲 channel 等待启动完成,再用有缓冲 channel 传数据”。这时 close 往往被放在错误位置:在所有数据发完后立刻 close,但 worker 可能还在处理中;或等 worker 自己 close,却忘了它可能 panic 退出而没执行。
最稳妥的做法是让协调者(通常是启动 worker 的那个 goroutine)统一管理生命周期。
- worker 启动成功后,通过同步 channel 通知主 goroutine:“我好了”
- 主 goroutine 在确认 worker 就绪后,才开始发数据;所有数据发完,调用
close(dataCh) - worker 内部用
for range dataCh安全消费,无需额外判断 - 如果 worker 可能提前退出,主 goroutine 应监听其 done channel,并在必要时主动
close数据 channel 防止 hang
同步通知靠的是 channel 的阻塞语义本身,异步解耦靠的是缓冲 + 显式生命周期控制。两者边界看似清晰,实际混用时,close 由谁触发、何时触发、是否一定被执行,才是多数死锁和数据丢失的根源。











