该错误表示程序死锁,Go 运行时主动 panic;需立即检查 panic 日志末尾的 goroutine 堆栈,重点查看 main goroutine 阻塞位置、channel 收发状态及锁/通道关闭情况。

看到 fatal error: all goroutines are asleep - deadlock! 就该停手查栈
这不是卡顿,是 Go 运行时确认程序彻底走不下去后主动 panic。它只在所有 goroutine 全部阻塞、且没有任何一个能被唤醒(比如没人收 channel、没人放锁、没人关通道)时触发。
- 第一反应不是加
time.Sleep或重跑,而是立刻看 panic 日志末尾的 goroutine 堆栈 - 重点盯三类信息:
maingoroutine 停在哪一行(比如卡在ch 或 <code>wg.Wait());其他 goroutine 是否全卡在同一个 channel 操作(如都在chan receive);有没有 goroutine 卡在select {}或空for里 - 日志被截断?加环境变量运行:
GODEBUG=schedtrace=1000 go run main.go,每秒输出调度快照,看goroutines:行是否长期为 0
go tool trace 能看出谁在等谁、等了多久
pprof 的 goroutine profile 只能看到栈帧,而 go tool trace 能还原 goroutine 状态变迁,对死锁更直接。
- 启动程序前加
import _ "net/http/pprof",并在main开头起服务:go http.ListenAndServe("localhost:6060", nil) - 运行后访问
http://localhost:6060/debug/trace?seconds=5下载trace.out - 执行
go tool trace trace.out,打开 Web UI,在 “Goroutine” 标签页筛选状态为chan recv或chan send的 goroutine - 典型线索:某 goroutine 长时间处于
chan recv状态,且无对应 sender 唤醒,基本就是死锁源头;注意无缓冲 channel 最容易暴露阻塞点
无缓冲 channel 必须“收发同时就位”,否则必死锁
无缓冲 channel 的本质是同步握手:发送方必须等到接收方 ready 才能继续。漏掉任意一端,就是死锁温床。
-
ch := make(chan int)后直接ch → 立即死锁(没接收方) -
for v := range ch却忘了close(ch)→ 接收方永久等待(发送方退出后无其他 goroutine 活跃) - 单个 goroutine 对无缓冲 channel 先发后收(或先收后发)必然死锁,例如:
ch := make(chan int); ch - 多个 goroutine 共用一个 channel,但关闭逻辑分散:A 发完 close(ch),B 还在 send →
panic: send on closed channel;C 忘关 →range永远等不到 EOF
select 没 default 且所有 case 都阻塞 → 主 goroutine 挂起即死锁
select 在所有 case 都不可执行时会挂起当前 goroutine。如果这是主 goroutine,且没有其他活跃协程,就触发死锁。
- 常见错误场景:只读或只写 channel、没加
default也没关 channel 的循环里 - 正确做法:加
default分支做非阻塞轮询(适合降级或探测),或配合time.After做超时控制(适合必须拿到结果的路径) - 性能注意:纯
default会导致高频空转,高频率下建议搭配runtime.Gosched()或小延时(如time.Sleep(time.Microsecond)) - 示例:
select { case v, ok := —— 这能防卡死,但不解决通信逻辑本身是否合理
真正难排查的往往不是“全阻塞”,而是某个 goroutine 拿着资源不放、别人干等——比如 sync.RWMutex 在同一个 goroutine 里多次 RLock(),或者 mu.Lock() 后 panic 导致 Unlock() 漏掉。这种不会触发 all goroutines are asleep,但会让程序静默卡住。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











