无缓冲channel的发送和接收必须在不同goroutine中进行,否则必然死锁;错误提示为“fatal error: all goroutines are asleep - deadlock!”,但不指明具体阻塞位置。

无缓冲channel必须配对goroutine收发
无缓冲channel的发送和接收必须发生在不同goroutine,否则必然死锁。运行时报错是fatal error: all goroutines are asleep - deadlock!,但错误位置往往不是你怀疑的那一行——它只告诉你“所有goroutine都卡住了”,不指明谁在等谁。
典型错误写法:
func main() {
ch := make(chan int)
ch <p>修正要点:</p>
- 发送和接收逻辑必须拆到至少两个goroutine中
- 如果用
go func() { ... }()启动接收,注意避免变量捕获问题(比如循环变量) - 不要依赖
time.Sleep来“凑时间差”,那是竞态隐患,不是同步方案
range遍历channel前必须确保它会被关闭
写for v := range ch时,Go会一直等待新数据,直到ch被close()。如果没人关,这个for就永远卡在recv操作上,哪怕缓冲区已空。
常见误用场景:
- 生产者goroutine提前退出,没调用
close(ch) - 多个生产者,但只关了一部分,或关早了(还有数据没发完)
- 用
sync.WaitGroup协调时,Wait()和close()顺序错乱
安全做法是:由**最后一个完成生产的goroutine**负责关闭。例如:
var wg sync.WaitGroup
go func() {
defer wg.Done()
for _, task := range tasks {
ch <h3>缓冲channel满后仍会阻塞,不能当队列无限使用</h3><p>缓冲channel不是“不会阻塞”的保障,只是把阻塞延迟到缓冲耗尽那一刻。一旦<code>len(ch) == cap(ch)</code>,下一次<code>或<code>ch 就会像无缓冲一样挂起。</code></code></p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill5461" title="Cross-Channel Notify"><img
src="https://img.php.cn/upload/skill/000/000/081/179050330744021.jpg" alt="Cross-Channel Notify" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill5461" title="Cross-Channel Notify" class="overflowclass">Cross-Channel Notify</a>
<p class="overflowclass">一次同时通过邮件(Himalaya)和iMessage(BlueBubbles)发送相同通知。用于用户想要通过多渠道广播或通知某人时使用。</p>
</div>
<a rel="nofollow" href="/xiazai/skill5461" title="Cross-Channel Notify" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div><p>排查时重点关注:</p>
- 消费者处理速度是否明显低于生产者(比如日志写入慢于采集)
- 缓冲容量是否与业务吞吐量匹配(10条缓冲撑不住每秒1000条消息)
- 是否用了
select配合default做非阻塞兜底,还是直接硬塞
示例(避免硬塞):
select {
case ch <h3>多channel协作时容易形成等待环</h3><p>两个或多个channel交叉收发,极易构成循环等待。比如A等B的数据,B又等A的数据,双方都在<code>上停住,runtime检测为死锁。</code></p><p>典型反模式:</p><pre class="brush:php;toolbar:false;">ch1 := make(chan int)
ch2 := make(chan int)
go func() { ch2 <p>这类问题在线上常隐藏在函数调用链深处,调试难度高。预防建议:</p>
- 避免在goroutine启动前就对channel做双向依赖
- 用单向channel(
chan / <code>)在函数签名中固化流向,编译期就能拦住反向操作 - 复杂流程优先用状态机+
select控制流转,而不是靠channel接力传递控制权
真正难缠的从来不是报错的死锁,而是那些没触发all goroutines are asleep、却让goroutine越积越多的隐性阻塞——它们往往卡在某个channel操作上,但总有一个goroutine在轮询或sleep,骗过了runtime的检测。查这类问题,得靠pprof抓goroutine堆栈,看哪几行反复出现chan send或chan recv。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










