

新闻资讯
技术学院死锁发生时Go运行时panic并打印fatal error,程序彻底卡死;通过panic日志中所有goroutine堆栈定位阻塞点,重点关注main goroutine停顿位置、channel操作及锁持有状态。
死锁发生时,Go 运行时会直接 panic 并打印 fatal error: all goroutines are asleep - deadlock! —— 这不是偶发卡顿,而是程序已彻底无法推进,必须立刻定位阻塞点。
Go 默认会在死锁时输出所有 goroutine 的当前堆栈,这是最直接的线索。重点盯三类信息:
main goroutine 停在哪一行?比如卡在 ch 或 mu.Lock()
chan receive)或同一把锁上?说明资源被单点垄断select {} 或空 for 循环里?那是典型的“忘了退出条件”如果日志被截断,加环境变量运行:
GODEBUG=schedtrace=1000 go run main.go每秒输出调度快照,观察
g 数量是否长期为 0 —— 是,就确认全部阻塞。
无缓冲 channel 是死锁高发区:发送前必须有接收方就位,否则发送方永久阻塞;range 接收前必须有人 close,否则无限等待。
len(ch) == cap(ch) 判断满、len(ch) == 0 判断空,仅对有缓冲 channel 有效;无缓冲 channel 无法用长度判断可读/可写close(ch),且只 close 一次;接收方要配合 value, ok := 判断是否已关闭
临时规避盲等:用 select { case ch 加 default 分支防阻塞。
mutex 死锁不报错,但会让 goroutine 卡在 sync.(*Mutex).Lock,pprof 查到后得人工逆向分析谁拿了没放。
mu.Lock()(没 Unlock)→ 直接卡死defer mu.Unlock() 写在 if 分支里
,或提前 return 漏掉 → 锁永远不释放mu1 再锁 mu2,B 反过来先 mu2 后 mu1 → 经典循环等待建议:给 mutex 字段加注释说明保护哪些变量;复杂逻辑优先用 channel 通信代替共享内存加锁。
死锁不一定触发 runtime panic(比如部分 goroutine 阻塞但主 goroutine 还活着),这时需主动排查:
_ "net/http/pprof",启动 HTTP 服务:go func() { http.ListenAndServe("localhost:6060", nil) }(),访问 http://localhost:6060/debug/pprof/goroutine?debug=2 查看实时堆栈go run -race main.go 检测数据竞争 —— 虽不直接报死锁,但竞态常是死锁前兆(比如两个 goroutine 都试图修改同一 map 而加锁顺序不一致)-race 和超时限制,让死锁在集成阶段暴露真正难缠的死锁往往只在高并发压测几小时后复现,靠日志和 pprof 快照很难抓到瞬间状态 —— 所以设计时就要避免“依赖对方先动”,比如用带缓冲 channel、设超时、加 context 控制生命周期,比事后调试更可靠。