Golang强制死锁检测器输出跟踪日志的方法
Golang强制死锁检测器输出跟踪日志的方法 我想使用执行跟踪包来通过研究收集到的跟踪信息调试死锁。然而,当发生死锁时,系统会崩溃而不产生任何跟踪信息。
死锁是通过checkdead()检测的,该函数检查正在运行的 goroutine 数量是否为 0,如果是则抛出致命错误。致命错误(与 panic 不同)是不可捕获和恢复的。它们是一条输出消息,后跟一个 sys.exit(1)。因此,在死锁情况下使用 recover() 调用 StopTrace()(以刷新跟踪缓冲区)的想法是不可行的。
所以我在想,也许我可以修改 checkdead() 函数,使其在抛出致命错误之前先调用 StopTrace()。我的意思是把下面这段代码(来自 checkdead() 声明):
...
getg().m.throwing = -1 // do not dump full stacks
unlock(&sched.lock) // unlock so that GODEBUG=scheddetail=1 doesn't hang
throw("all goroutines are asleep - deadlock!")
...
改成:
...
getg().m.throwing = -1 // do not dump full stacks
unlock(&sched.lock) // unlock so that GODEBUG=scheddetail=1 doesn't hang
StopTrace() // to flush trace buffers before exit
throw("all goroutines are asleep - deadlock!")
...
然而,由于编译指令(#pragmas)强制某些函数不能有写屏障,当我想重新构建运行时库时,上面的代码片段会导致崩溃。例如,runtime 包中的函数 templateThread() 在执行期间会调用 checkdead(),并且它带有 //go:nowritebarriersrec 编译指令,这意味着该函数及其调用的任何函数都不能有写屏障。
综上所述,我有两个问题:
- 有没有人知道更好的方法,如何强制
checkdead()在退出程序之前刷新跟踪缓冲区? - 有没有办法绕过编译指令以避免构建崩溃?虽然这可能会导致运行时行为异常。我只是好奇。
附注:我不确定我把问题解释得是否清楚。如果有任何部分不清楚,请在评论中告诉我。
更多关于Golang强制死锁检测器输出跟踪日志的方法的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于Golang强制死锁检测器输出跟踪日志的方法的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
要强制死锁检测器输出跟踪日志,可以通过修改运行时源码实现。以下是具体方法:
- 修改
runtime/proc.go中的checkdead()函数:
// 在抛出死锁错误前停止跟踪
func checkdead() {
// ... 原有检查逻辑 ...
getg().m.throwing = -1
unlock(&sched.lock)
// 添加跟踪停止逻辑
if trace.enabled {
traceStop()
}
throw("all goroutines are asleep - deadlock!")
}
- 编译自定义运行时:
# 克隆Go源码
git clone https://go.googlesource.com/go
cd go/src
# 应用修改后编译
./make.bash
- 使用修改后的运行时编译程序:
// main.go - 示例死锁程序
package main
import (
"runtime/trace"
"sync"
)
func main() {
trace.Start(os.Stderr)
defer trace.Stop()
var mu sync.Mutex
mu.Lock()
mu.Lock() // 第二次锁定导致死锁
}
编译命令:
# 使用自定义运行时
GOROOT=/path/to/custom/go ./go/bin/go build main.go
- 处理写屏障限制的替代方案:
// 通过runtime包直接调用内部函数
// 在checkdead()中添加:
if trace.enabled {
// 直接调用内部停止函数,避免写屏障
stopTrace()
}
其中 stopTrace() 需要在 runtime 包中实现:
// runtime/trace.go
func stopTrace() {
// 简化的停止逻辑,避免写屏障
traceLock()
if trace.enabled {
traceShutdown()
}
traceUnlock()
}
- 使用信号处理作为备选方案:
// 在程序启动时注册信号处理
import (
"os"
"os/signal"
"runtime/trace"
"syscall"
)
func main() {
trace.Start(os.Stderr)
// 捕获退出信号
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGABRT, syscall.SIGQUIT)
go func() {
<-c
trace.Stop()
os.Exit(1)
}()
// ... 程序逻辑 ...
}
这种方法通过修改运行时源码,在 checkdead() 检测到死锁时强制停止跟踪并输出日志。需要重新编译Go工具链,但可以确保在死锁发生时获取跟踪信息。

