可以把 Go 这三个东西理解成分别回答三个完全不同的问题:
| 工具 | 核心问题 | 关注对象 | 常用命令 |
|---|---|---|---|
| Race | 「有没有多个 goroutine 在同时乱改数据?」 | 并发内存访问 | go test -race ./... |
| Trace | 「程序运行时到底发生了什么?」 | Goroutine、调度、GC、阻塞 | go test -trace=trace.out |
| 逃逸分析 | 「这个变量为什么跑到堆上去了?」 | 内存分配 | go build -gcflags="-m" |
1. Race:查并发 Bug
var n int
go func() { n++ }()
go func() { n++ }()
Race Detector 会告诉你:
Goroutine A 正在写 n
Goroutine B 同时也在读/写 n
↓
DATA RACE
它关注的是内存访问之间有没有正确同步。
2. Trace:查运行时行为
例如你的服务突然:
CPU 很高
↓
到底是哪个 goroutine 在跑?
↓
是不是 GC?
是不是锁阻塞?
是不是 syscall?
是不是网络等待?
Trace 会把运行过程记录下来,然后:
go tool trace trace.out
你可以看到 Goroutine 的运行、阻塞、唤醒,以及 GC、调度器等信息。
所以它更像:
给 Go 程序装一个“黑匣子”,看看程序运行过程中到底发生了什么。
3. 逃逸分析:查内存分配
func foo() *int {
x := 10
return &x
}
编译器发现:
x escapes to heap
因为 foo() 返回以后 x 还必须存在。
另一个例子:
func foo() {
x := 10
fmt.Println(x)
}
编译器可能判断 x 不需要逃逸到堆。
它主要回答:
这个对象能不能安全地留在栈上?如果不能,就放堆上。
最简单的记忆方式
Race
↓
并发有没有撞车? 🚗💥
Trace
↓
程序运行时发生了什么? 🔍
Escape Analysis
↓
变量到底该放栈还是堆? 📦
而且三者解决的问题层次也不同:
Go 程序
│
┌───────────┼───────────┐
↓ ↓ ↓
Race Trace 逃逸分析
│ │ │
并发正确性 运行时性能 内存分配
实际排查问题时:
- 怀疑 goroutine 数据竞争 →
-race - 怀疑 goroutine/GC/调度导致性能问题 →
trace - 怀疑大量堆分配、GC 压力 →
-gcflags="-m"+pprof
这三个基本可以看成 Go 后端开发里非常重要的三把“手术刀”。