只要进程未崩溃且含调试符号,dlv attach 可实时诊断 go 服务卡死问题:查 pid 后 attach,用 goroutines 和 bt 定位 channel 死锁或 mutex 持有者,比 pprof/sigquit 更精准可控。
线上 go 服务 cpu 飙高或完全卡死、无响应,但没 panic?别急着 kill -9 或重启——只要进程还在且编译时保留了调试符号(即没加 -ldflags="-s -w"),dlv attach 就能直接“闯入”正在运行的进程,实时查看 goroutine 状态和阻塞点。
怎么用 dlv attach 到卡死的服务进程
核心前提是:服务二进制必须含调试信息(默认开启),且 Linux 允许 ptrace(kernel.yama.ptrace_scope=0)。
- 查 PID:
ps aux | grep your_service_name,确认进程确实在运行 - 临时放开调试权限(仅限排查时):
sudo sysctl -w kernel.yama.ptrace_scope=0 - attach:
dlv attach <pid></pid>,成功后进入交互式调试器 - 立即执行:
goroutines—— 这会列出所有 goroutine ID、状态(running、chan receive、IO wait、semacquire等)和简略栈顶函数 - 重点关注状态为
chan send或chan receive且长时间不动的 goroutine,它们大概率是死锁/阻塞源头
如何快速定位 channel 死锁或 mutex 持有者
死锁常表现为 “所有 goroutine 都在等对方”,dlv 能直接看到谁在等什么。
- 挑一个可疑 goroutine,比如 ID 是
17:goroutine 17切换过去,再执行bt查看完整调用栈 - 栈中若出现
runtime.chansend或runtime.chanrecv,说明卡在 channel 操作;结合代码确认该 channel 是否有对应接收方/发送方 - 若栈里有
sync.(*Mutex).Lock或sync.(*RWMutex).RLock且调用链很深,用p m.state(假设变量名是m)查看 mutex 当前状态(是否已被锁定、由哪个 goroutine 持有) - 注意:
dlv无法直接显示锁持有者 goroutine ID,但可通过goroutines -u(显示用户代码栈)配合栈中函数名交叉比对
为什么不能只靠 pprof / SIGQUIT,而要上 dlv
SIGQUIT(Ctrl+\)和 pprof/goroutine?debug=2 只给快照,dlv 提供可交互的上下文。
-
SIGQUIT输出堆栈不可筛选,上千 goroutine 时人眼难定位;dlv支持goroutines -s chan过滤出所有 channel 相关状态 -
pprof的 goroutine profile 是文本 dump,没法查变量值;dlv中可用p ch.len、p len(mySlice)实时看 channel 缓冲长度或切片当前元素数 - 若卡死由竞态引发(如两个 goroutine 同时修改 map),
-race编译的二进制在dlv中仍可观察内存地址变化,但需提前编译 - 重要限制:
dlv attach不能修改运行中代码逻辑,也不能恢复被中断的系统调用(如卡在accept的 net.Conn)
生产环境 attach 的几个硬性前提
不是所有线上环境都能顺利 attach,这些细节决定成败。
- 二进制必须未 strip:构建时避免
go build -ldflags="-s -w";CI/CD 流水线建议保留.debug段 - CGO_ENABLED=0 不影响
dlv工作,但 musl libc 或静态链接到 busybox 的镜像可能不支持 ptrace,attach 会报could not attach to pid - 容器内 attach 需确保容器以
--cap-add=SYS_PTRACE启动,且宿主机ptrace_scope已调低 - 如果服务用了
seccompprofile,需显式允许ptrace系统调用,否则 attach 失败且无明确错误提示
真正卡死时,最危险的错觉是“它只是慢”。goroutine 阻塞、channel 无人收发、mutex 持有者已崩溃却未释放——这些状态不会自己恢复。dlv attach 不是万能锤,但它能让你在进程还活着的时候,看清调度器眼里那个“静止”的瞬间。别等它自己醒来,主动进去看看它卡在哪一行、等哪一个 channel、握着哪一把锁。











