Skip to content

Goroutine 问题排查:官方依据、观测入口与常见错误清单

概览

基于 Go 官方文档整理 goroutine 排查实践,覆盖 goroutine 生命周期边界、runtime.NumGoroutine、runtime.Stack、net/http/pprof、goroutine profile、block profile、mutex profile、runtime/trace、go vet、race detector、goroutine 泄漏、死锁、channel 阻塞、closed channel panic、WaitGroup 误用、Mutex 与 RWMutex 竞争、context 取消、main 生命周期、panic、无界 goroutine 创建、外部 I/O 阻塞、select 等待与标准排查流程。

1. 概念边界

Go 语言规范将 go 语句描述为启动一个独立并发执行的控制线程,即 goroutine。该 goroutine 与当前 goroutine 运行在同一地址空间中。调用 go f() 后,函数调用会在新的 goroutine 中开始执行,当前程序执行流程不会等待该函数完成。当函数返回时,该 goroutine 终止。1

因此,在 Go 程序中排查 goroutine 问题时,核心对象不是操作系统线程本身,而是 goroutine 的数量、生命周期、阻塞位置、调度关系、同步关系、共享内存访问关系以及取消传播关系。

Go 语言规范还说明:当 main 函数返回时,程序退出,不会等待其他非 main goroutine 完成。1 因此,“goroutine 未执行完成”与“main 提前退出”属于生命周期问题。


2. 官方文档对应的排查入口

2.1 goroutine 数量观测

runtime.NumGoroutine() 返回当前存在的 goroutine 数量。Go 官方诊断文档说明,该指标可用于监控 goroutine 数量,并用于检测 goroutine 泄漏。2

常见使用方式:

go
package main

import (
	"log"
	"runtime"
	"time"
)

func main() {
	ticker := time.NewTicker(10 * time.Second)
	defer ticker.Stop()

	for range ticker.C {
		// Print the current number of existing goroutines.
		log.Printf("goroutines=%d", runtime.NumGoroutine())
	}
}

排查用途:

现象观测方式
goroutine 数量持续上升周期性记录 runtime.NumGoroutine()
压测结束后数量不回落比较压测前、压测中、压测后的数量
某接口调用后数量增加在接口入口、出口、异步任务启动点记录数量
线上周期性增长将 goroutine 数量作为运行时指标上报

2.2 goroutine 堆栈抓取

runtime.Stack(buf, true) 可以将当前 goroutine 以及其他 goroutine 的堆栈写入缓冲区。2

示例:

go
package debugutil

import "runtime"

func DumpAllGoroutines() []byte {
	buf := make([]byte, 1<<20)

	for {
		// Write stack traces for all goroutines.
		n := runtime.Stack(buf, true)
		if n < len(buf) {
			return buf[:n]
		}

		// Grow the buffer when it is not large enough.
		buf = make([]byte, len(buf)*2)
	}
}

排查用途:

堆栈信息对应问题
大量 goroutine 停在同一 channel receive接收端等待数据、发送端未发送、channel 未关闭、nil channel
大量 goroutine 停在同一 channel send发送端阻塞、接收端不足、buffer 满、nil channel
大量 goroutine 停在 sync.(*WaitGroup).WaitDone 未执行、Add/Wait 顺序错误、计数器未归零
大量 goroutine 停在 sync.(*Mutex).Lock锁竞争、锁未释放、锁顺序导致互相等待
大量 goroutine 停在 I/O 调用网络、文件、系统调用或外部依赖阻塞
大量 goroutine 停在 select等待多个事件,但所有 case 均未就绪

2.3 net/http/pprof

net/http/pprof 官方文档说明,该包通过 HTTP 暴露运行时 profiling 数据,数据格式可被 pprof 工具读取。导入该包的副作用会注册 /debug/pprof/ 下的 HTTP handler。3

最小接入方式:

go
package main

import (
	"log"
	"net/http"
	_ "net/http/pprof"
	"runtime"
)

func main() {
	// Enable block profiling. A rate of 1 records every blocking event.
	runtime.SetBlockProfileRate(1)

	// Enable mutex profiling. A rate of 1 records every contention event.
	runtime.SetMutexProfileFraction(1)

	go func() {
		// Expose pprof endpoints on localhost only in this example.
		log.Println(http.ListenAndServe("localhost:6060", nil))
	}()

	select {}
}

常用命令:

bash
# List available pprof profiles.
curl http://localhost:6060/debug/pprof/

# Capture goroutine stack traces in text form.
curl -o goroutine.txt "http://localhost:6060/debug/pprof/goroutine?debug=2"

# Analyze goroutine profile.
go tool pprof http://localhost:6060/debug/pprof/goroutine

# Analyze heap profile.
go tool pprof http://localhost:6060/debug/pprof/heap

# Capture a 30-second CPU profile.
go tool pprof "http://localhost:6060/debug/pprof/profile?seconds=30"

# Analyze block profile after runtime.SetBlockProfileRate is enabled.
go tool pprof http://localhost:6060/debug/pprof/block

# Analyze mutex profile after runtime.SetMutexProfileFraction is enabled.
go tool pprof http://localhost:6060/debug/pprof/mutex

# Capture execution trace.
curl -o trace.out "http://localhost:6060/debug/pprof/trace?seconds=5"

# Open execution trace.
go tool trace trace.out

Go 官方诊断文档列出的 profile 包括:

profile官方描述对应的排查对象
goroutine当前所有 goroutine 的堆栈
heap堆内存分配
threadcreate操作系统线程创建
blockgoroutine 在同步原语上阻塞的位置
mutex锁竞争位置
profileCPU profile
trace执行追踪

其中,block profile 默认未开启,需要调用 runtime.SetBlockProfileRate;mutex profile 默认未开启,需要调用 runtime.SetMutexProfileFraction3


2.4 runtime/trace

runtime/trace 官方文档说明,执行 trace 会捕获 goroutine 创建、阻塞、解除阻塞、系统调用进入、系统调用退出、系统调用阻塞、GC 事件、堆大小变化、处理器启动与停止等事件。4

排查用途:

问题trace 观察点
goroutine 创建过多goroutine creation 事件
goroutine 长时间阻塞blocking / unblocking 事件
外部调用耗时syscall enter / exit / block 事件
调度延迟goroutine runnable 到 running 的时间
GC 对延迟的影响GC 事件与 goroutine 执行时间线

测试阶段可使用:

bash
go test -trace=trace.out ./...
go tool trace trace.out

运行中服务可使用:

bash
curl -o trace.out "http://localhost:6060/debug/pprof/trace?seconds=5"
go tool trace trace.out

2.5 go vet

go vet 官方文档说明,该工具检查 Go 源码中可疑结构。其检查项包括 lostcancelcopylocksloopclosurewaitgroup 等。8

常用命令:

bash
go vet ./...

与 goroutine 相关的典型检查项:

vet 检查项对应问题
lostcancelcontext.WithCancelcontext.WithTimeoutcontext.WithDeadline 返回的 cancel 未调用
copylockssync.Mutexsync.WaitGroup 等锁相关对象被复制
loopclosuregoroutine 闭包引用循环变量
waitgroupWaitGroup.Add 在 goroutine 内部调用,可能与 Wait 产生竞态

2.6 Race Detector

Go 官方 race detector 文档说明:当两个 goroutine 并发访问同一变量,且至少一个访问是写操作时,如果不存在同步约束,则发生 data race。7

常用命令:

bash
go test -race ./...
go run -race ./cmd/app
go build -race ./cmd/app

race detector 报告包含发生冲突的访问堆栈,以及相关 goroutine 的创建堆栈。7

注意事项:

官方事实排查含义
race detector 只检测运行时实际发生的 race需要通过测试或运行流量覆盖相关路径
race detector 会带来额外内存和执行时间开销生产环境常驻启用需要单独评估
报告包含 goroutine 创建栈可定位共享变量被哪些 goroutine 访问

3. Goroutine 常见错误与排查手段

3.1 goroutine 异常增多 / goroutine leak

现象:

现象表现
goroutine 数量持续增加runtime.NumGoroutine() 曲线单调上升或周期性抬升
请求结束后 goroutine 不释放压测停止后 goroutine 数量不回落
相同堆栈重复出现pprof/goroutine?debug=2 中大量 goroutine 停在相同位置
内存同步增长goroutine 数量上升伴随堆内存、栈内存、timer 或上下文对象增长

官方依据:

  • runtime.NumGoroutine() 返回当前存在的 goroutine 数量。2
  • goroutine profile 报告当前所有 goroutine 的堆栈。3
  • context 文档说明,不调用 CancelFunc 会泄漏子 context 及其子节点,直到父 context 被取消。5

排查步骤:

bash
# 1. 记录当前 goroutine 堆栈。
curl -o goroutine_1.txt "http://localhost:6060/debug/pprof/goroutine?debug=2"

# 2. 间隔一段时间后再次记录。
curl -o goroutine_2.txt "http://localhost:6060/debug/pprof/goroutine?debug=2"

# 3. 对比重复增长的堆栈。
diff -u goroutine_1.txt goroutine_2.txt

代码检查点:

检查点对应问题
go func() 是否在循环、请求、消息消费中无限制创建goroutine 创建速率超过退出速率
goroutine 是否监听 ctx.Done()上游取消后异步任务是否能退出
CancelFunc 是否在所有控制路径调用context 子节点和 timer 是否释放
channel 是否存在永不关闭或永不发送goroutine 是否永久阻塞
ticker 是否 Stop周期任务资源是否释放
外部 I/O 是否有超时网络、数据库、RPC 调用是否长期阻塞

3.2 进程级死锁

现象:

text
fatal error: all goroutines are asleep - deadlock!

Go runtime 源码中存在该 fatal 信息。当所有 goroutine 均处于不可继续运行的状态时,runtime 会触发该错误。[10]

常见触发形态:

形态示例
main goroutine 等待 channel receive,但没有任何 sender<-ch
main goroutine 等待 channel send,但没有任何 receiverch <- v
所有 goroutine 都等待同一个 WaitGroupwg.Wait()
goroutine 之间锁顺序互相等待A 持有 lock1 等 lock2,B 持有 lock2 等 lock1
nil channel send / receivevar ch chan int; <-ch

排查步骤:

bash
# Reproduce with all goroutine traceback.
GOTRACEBACK=all ./app

或在运行中抓取:

bash
curl -o goroutine.txt "http://localhost:6060/debug/pprof/goroutine?debug=2"

判断方式:

堆栈状态判断方向
全部停在 channel send / receivechannel 通信双方不完整
全部停在 WaitGroup.WaitWaitGroup 计数器未归零
多个 goroutine 分别停在不同锁的 Lock锁顺序或锁释放路径问题
goroutine 停在 nil channel 相关代码nil channel 未初始化或 select 逻辑错误

3.3 channel send / receive 永久阻塞

官方依据:

  • channel 提供并发 goroutine 间通信机制。9
  • unbuffered channel 的通信只有在 sender 与 receiver 都准备好时才能完成。9
  • nil channel 永远不会 ready。9
  • send 到 nil channel 会永久阻塞。9
  • receive from nil channel 会永久阻塞。9

错误形态:

go
func blockOnNilChannel() {
	var ch chan int

	// This receive blocks forever because ch is nil.
	<-ch
}
go
func blockOnSend() {
	ch := make(chan int)

	// This send blocks because there is no receiver.
	ch <- 1
}

排查手段:

手段作用
goroutine stack找出阻塞在 send 还是 receive
block profile找出 goroutine 阻塞在同步原语的位置
trace查看 goroutine blocking 与 unblocking 的时间线
代码审查检查 channel 初始化、关闭、发送方、接收方、buffer 容量

命令:

bash
go tool pprof http://localhost:6060/debug/pprof/block
curl -o trace.out "http://localhost:6060/debug/pprof/trace?seconds=5"
go tool trace trace.out

3.4 send on closed channel / close closed channel / close nil channel

官方依据:

  • send 到已经关闭的 channel 会 panic。9
  • close 已经关闭的 channel 会 panic。9
  • close nil channel 会 panic。9
  • receive 已关闭且已无剩余值的 channel,会立即返回该元素类型零值。9

错误形态:

go
func sendClosedChannel() {
	ch := make(chan int)
	close(ch)

	// This panics because the channel is already closed.
	ch <- 1
}

排查手段:

现象排查方式
panic: send on closed channel查看 panic stack,定位发送方
panic: close of closed channel查看 panic stack,定位重复关闭方
panic: close of nil channel检查 channel 初始化路径
偶发 panic使用 go test -race 检查 send / close 是否并发发生

Race Detector 官方文档中包含“unsynchronized send and close operations”的典型案例。7


3.5 WaitGroup 使用错误

官方依据:

  • sync.WaitGroup 是用于等待一组任务完成的计数信号量。6
  • Add 会向 WaitGroup 计数器增加 delta。6
  • 计数器变为负数会 panic。6
  • Done 等价于 Add(-1)6
  • Wait 会阻塞直到计数器归零。6
  • go vetwaitgroup analyzer 会检测在新 goroutine 内调用 WaitGroup.Add 的误用。8

常见错误:

错误现象
Add(1) 后缺少 Done()Wait() 永久阻塞
Done() 调用次数多于 Add()panic: sync: negative WaitGroup counter
在 goroutine 内部调用 Add()Add 可能与 Wait 并发产生竞态
复制 WaitGroup多个副本计数器不一致
goroutine panic 后未执行 Done()Wait() 阻塞

错误示例:

go
func wrongWaitGroup() {
	var wg sync.WaitGroup

	go func() {
		// Wrong: Add may race with Wait.
		wg.Add(1)
		defer wg.Done()
	}()

	wg.Wait()
}

排查手段:

bash
go vet ./...

运行时排查:

bash
curl -o goroutine.txt "http://localhost:6060/debug/pprof/goroutine?debug=2"

堆栈判断:

堆栈含义
sync.(*WaitGroup).Wait当前 goroutine 等待计数器归零
panic: sync: negative WaitGroup counterDoneAdd(-1) 次数超过 Add
多个 goroutine 停在 Wait计数器未归零或任务退出路径异常

3.6 Mutex / RWMutex / Cond 相关阻塞

Go 官方诊断文档说明:

  • block profile 显示 goroutine 在同步原语上阻塞的位置。
  • mutex profile 报告锁竞争。
  • block profile 默认未开启,需要 runtime.SetBlockProfileRate
  • mutex profile 默认未开启,需要 runtime.SetMutexProfileFraction3

排查手段:

go
func enableProfiles() {
	// Enable block profiling. A rate of 1 records every blocking event.
	runtime.SetBlockProfileRate(1)

	// Enable mutex profiling. A rate of 1 records every contention event.
	runtime.SetMutexProfileFraction(1)
}
bash
go tool pprof http://localhost:6060/debug/pprof/block
go tool pprof http://localhost:6060/debug/pprof/mutex

常见错误:

错误现象排查方式
加锁后未解锁goroutine 停在 Lockgoroutine stack + mutex profile
锁顺序不一致多个 goroutine 互相等待goroutine stack
持锁执行慢 I/Omutex profile 显示长时间竞争mutex profile + trace
复制包含锁的结构体锁状态被复制go vet -copylocks
Cond 等待条件未满足goroutine 停在 Cond.Waitgoroutine stack

3.7 Data Race

官方依据:

Go 官方文档定义:当两个 goroutine 并发访问同一变量,并且至少一个访问是写操作时,如果不存在同步关系,则发生 data race。7

常见形态:

错误示例
goroutine 闭包共享循环变量多个 goroutine 读写同一循环变量
map 并发读写一个 goroutine 写 map,另一个 goroutine 读 map
全局变量未加锁多 goroutine 读写 package-level 变量
channel send 与 close 未同步一个 goroutine send,另一个 goroutine close
基础类型变量并发读写bool、int、指针等直接读写

排查命令:

bash
go test -race ./...
go run -race ./cmd/app
go build -race ./cmd/app

报告读取重点:

报告字段用途
conflicting access stack定位冲突读写位置
goroutine creation stack定位 goroutine 启动点
read/write 标识判断哪个路径写入共享变量
file:line定位源码行

3.8 循环变量闭包捕获

go vetloopclosure analyzer 官方文档说明:在 Go 1.22 之前,循环变量生命周期可能导致闭包观察到错误的变量值;从 Go 1.22 开始,循环变量生命周期发生变化。8

Go 官方 race detector 文档也列出循环变量并发访问的典型 data race 示例。7

错误形态:

go
func wrongLoopCapture(values []int) {
	for _, v := range values {
		go func() {
			// In old loop variable semantics, this may capture the loop variable.
			println(v)
		}()
	}
}

兼容旧语义的写法:

go
func correctLoopCapture(values []int) {
	for _, v := range values {
		v := v

		go func() {
			// This goroutine captures the per-iteration value.
			println(v)
		}()
	}
}

排查命令:

bash
go vet ./...
go test -race ./...

3.9 context 未取消 / 取消信号未传播

官方依据:

context 官方文档说明:

  • Context 携带 deadline、cancellation signal 和 request-scoped values。
  • CancelFunc 会取消子 context 及其子 context,移除父 context 对子 context 的引用,并停止关联 timer。
  • 未调用 CancelFunc 会泄漏子 context 及其子节点,直到父 context 被取消。
  • Done() 返回一个 channel,该 channel 在相关工作需要取消时关闭。5

错误形态:

go
func wrongContext(parent context.Context) {
	ctx, _ := context.WithTimeout(parent, time.Second)

	go func() {
		select {
		case <-ctx.Done():
			return
		}
	}()
}

问题点:CancelFunc 未保存、未调用。

排查方式:

bash
go vet ./...

其中 lostcancel 检查项用于发现 cancel 未调用的问题。8

运行时排查:

现象排查方式
请求结束后 goroutine 仍存在查看 goroutine stack 是否等待 channel、I/O、timer
pprof 中存在大量相同业务 goroutine检查是否监听 ctx.Done()
定时器资源增长检查 WithTimeout / WithDeadline 是否调用 cancel
下游调用不退出检查 context 是否向下游传递

3.10 main 提前退出导致 goroutine 未完成

官方依据:

Go 语言规范说明:程序执行从初始化 main package 开始,然后调用 main 函数;当该函数返回时,程序退出,不会等待其他非 main goroutine 完成。1

错误形态:

go
func main() {
	go func() {
		// This goroutine may not finish before main returns.
		doWork()
	}()
}

现象:

现象判断方式
日志未完整输出main 返回前进程已退出
异步任务未执行完成goroutine 生命周期没有被等待
测试偶发失败测试函数返回时异步 goroutine 仍在运行

排查方式:

手段作用
添加退出日志判断 main 是否先返回
使用 WaitGroup 或其他同步机制使 main 等待任务完成
使用 go test -race检查异步 goroutine 是否访问测试结束后的共享状态

3.11 goroutine 内 panic

Go 语言规范说明:调用 panic 后,当前函数执行停止,延迟函数按照后进先出顺序执行;如果没有 recover,panic 会继续传播。[11]

常见现象:

现象排查方式
进程退出并输出 panic stack根据 stack 定位 panic goroutine
panic 发生在异步任务内查看 goroutine creation stack 或业务启动点
WaitGroup 等待不返回goroutine panic 后未执行 Done 或 recovery 路径异常

排查命令:

bash
GOTRACEBACK=all ./app

也可在程序中设置:

go
func init() {
	// Print all goroutine stacks when an unrecovered panic occurs.
	debug.SetTraceback("all")
}

3.12 无限制 goroutine 创建

现象:

现象表现
goroutine 数量与请求数、消息数或任务数同步增长runtime.NumGoroutine() 随输入量线性增长
goroutine profile 中大量启动点相同go func 位于循环、请求处理、消息消费等路径
trace 中 goroutine creation 事件密集短时间创建大量 goroutine

排查方式:

bash
curl -o goroutine.txt "http://localhost:6060/debug/pprof/goroutine?debug=2"

curl -o trace.out "http://localhost:6060/debug/pprof/trace?seconds=5"
go tool trace trace.out

代码检查点:

检查点说明
go func 是否在无界循环中goroutine 创建数量是否受输入控制
异步任务是否存在退出条件goroutine 是否能在任务完成或取消后退出
任务队列是否有容量边界创建速率是否可能超过处理速率
是否监听 ctx.Done()上游取消后 goroutine 是否退出

3.13 goroutine 阻塞在外部 I/O 或 syscall

runtime/trace 官方文档说明,trace 会捕获系统调用进入、退出以及阻塞事件。4

常见现象:

现象排查方式
goroutine stack 停在网络读写查看网络调用位置、超时配置
goroutine stack 停在数据库或 RPC 调用查看外部依赖调用位置
trace 中 syscall block 时间长使用 go tool trace 查看 syscall block
goroutine 数量增长但 CPU 不高检查是否大量 goroutine 等待 I/O

排查命令:

bash
curl -o goroutine.txt "http://localhost:6060/debug/pprof/goroutine?debug=2"

curl -o trace.out "http://localhost:6060/debug/pprof/trace?seconds=5"
go tool trace trace.out

3.14 select 永久等待

select 常见于等待 channel、context cancellation、timer 等事件。当所有 case 均不可继续,且没有 default 分支时,当前 goroutine 会阻塞。

常见形态:

go
func waitForever(ch <-chan int) {
	select {
	case <-ch:
		return
	}
}

排查方式:

现象排查方式
goroutine stack 停在 select查看每个 case 对应的 channel 或 context
context 取消后 goroutine 不退出检查 select 是否包含 <-ctx.Done()
timer 分支未触发检查 timer 创建、reset、stop 路径
channel 分支未触发检查发送方、关闭方、buffer 容量

4. 标准排查流程

4.1 先确认 goroutine 数量

bash
# If the application exports metrics, query the goroutine count metric.
# If not, expose runtime.NumGoroutine() in logs or diagnostics endpoints.

判断:

结果下一步
数量稳定排查重点转向局部阻塞、race、panic、外部 I/O
数量持续增长抓取 goroutine profile 并比较增长堆栈
数量周期性增长后回落结合业务周期、定时任务、连接池、队列消费排查
数量突然暴涨检查循环创建、请求风暴、消息堆积、外部调用阻塞

4.2 抓取 goroutine profile

bash
curl -o goroutine.txt "http://localhost:6060/debug/pprof/goroutine?debug=2"

分析维度:

维度内容
相同堆栈数量哪类 goroutine 数量最多
阻塞点channel、WaitGroup、Mutex、I/O、select
创建点go func 所在业务路径
是否包含 context是否监听 cancellation signal
是否集中在某接口或任务是否与业务流量入口相关

4.3 启用 block / mutex profile

go
func enableBlockingDiagnostics() {
	// Enable block profiling.
	runtime.SetBlockProfileRate(1)

	// Enable mutex profiling.
	runtime.SetMutexProfileFraction(1)
}
bash
go tool pprof http://localhost:6060/debug/pprof/block
go tool pprof http://localhost:6060/debug/pprof/mutex

适用问题:

profile适用场景
blockchannel、select、WaitGroup、Cond 等同步阻塞
mutexMutex / RWMutex 锁竞争

4.4 抓取 trace

bash
curl -o trace.out "http://localhost:6060/debug/pprof/trace?seconds=5"
go tool trace trace.out

适用问题:

问题trace 作用
goroutine 创建过多观察创建事件
goroutine 长时间 runnable 但未运行观察调度延迟
syscall 阻塞观察 syscall block
GC 影响延迟观察 GC 与 goroutine 执行时间线
任务链路复杂观察 goroutine unblock 关系

4.5 执行静态与动态检查

bash
go vet ./...
go test -race ./...

对应关系:

命令发现问题
go vetlostcancel、copylocks、loopclosure、waitgroup 等可疑结构
go test -race运行时真实发生的数据竞争
go run -race在本地运行过程中检测 race
go build -race构建带 race detector 的二进制

5. 常见错误总表

编号错误类型主要现象主要排查手段
1goroutine 异常增多 / 泄漏数量持续上升、请求结束不回落runtime.NumGoroutine、goroutine profile、context 检查
2进程级死锁all goroutines are asleep - deadlockGOTRACEBACK=all、goroutine stack、block profile
3channel receive 永久阻塞goroutine 停在 <-chgoroutine stack、channel 发送方检查
4channel send 永久阻塞goroutine 停在 ch <- vgoroutine stack、buffer/receiver 检查
5nil channel 阻塞send/receive 永久不继续channel 初始化路径检查
6send on closed channelpanicpanic stack、race detector
7close closed channelpanicpanic stack、关闭方检查
8close nil channelpanicchannel 初始化路径检查
9WaitGroup 缺少 DoneWait 永久阻塞goroutine stack、go vet
10WaitGroup Add/Wait 竞态偶发等待异常go vet -waitgroup、代码路径检查
11WaitGroup 计数为负panicpanic stack、Add/Done 次数检查
12Mutex 未释放goroutine 停在 Lockgoroutine stack、mutex profile
13锁顺序互等多 goroutine 互相等待goroutine stack、mutex profile
14复制锁对象锁状态异常go vet -copylocks
15Data Race非确定性结果、race 报告go test -race
16循环变量闭包捕获goroutine 使用错误变量值go vet -loopclosure、race detector
17context cancel 未调用context 子节点、timer 或 goroutine 不释放go vet -lostcancel、goroutine profile
18goroutine 未监听取消信号请求取消后任务仍运行goroutine stack、context 检查
19main 提前退出异步任务未完成main 生命周期检查、同步等待
20goroutine 内 panic进程 panic 或任务异常退出GOTRACEBACK=all、panic stack
21无限制 goroutine 创建数量随输入量快速增长goroutine profile、trace
22外部 I/O 阻塞goroutine 停在网络、RPC、DB 调用goroutine stack、trace
23select 永久等待goroutine 停在 selectgoroutine stack、case 条件检查
24Cond 等待未唤醒goroutine 停在 Cond.Waitgoroutine stack、block profile
25channel close 语义误用receive 到零值导致业务误判检查 receive 的 value, ok := <-ch 使用

6. 结论

Goroutine 问题排查可以归纳为五类客观证据:

  1. 数量证据:runtime.NumGoroutine()
  2. 堆栈证据:runtime.Stack/debug/pprof/goroutine?debug=2
  3. 阻塞证据:block profile、mutex profile。
  4. 时间线证据:runtime/tracego tool trace
  5. 代码证据:go vet、race detector、panic stack。

对于 goroutine 异常增多,直接证据是 goroutine 数量趋势与重复堆栈。对于死锁,直接证据是 runtime fatal 信息和所有 goroutine 的阻塞堆栈。对于 channel、WaitGroup、Mutex、context、data race 等问题,Go 官方文档均提供了对应的语义说明、运行时工具或静态检查入口。

参考资料

1 Go 语言规范说明 go 语句会启动一个独立并发执行的 goroutine,当前执行流程不会等待它完成;同时,main 返回后程序退出,不等待其他非 main goroutine。(Go)

2 Go 官方诊断文档说明 runtime.NumGoroutine 可用于监控 goroutine 数量并检测 goroutine leak;runtime.Stack 可输出当前及全部 goroutine 堆栈。(Go)

3 net/http/pprof 官方文档说明该包暴露 /debug/pprof/ profiling 入口;Go 官方诊断文档列出了 goroutine、heap、threadcreate、block、mutex 等 profile 的用途。(Go Packages)

4 runtime.SetBlockProfileRateruntime.SetMutexProfileFraction 用于开启 block 与 mutex profile;runtime/trace 官方文档说明 trace 会捕获 goroutine 创建、阻塞、解除阻塞、系统调用、GC 等事件。(Go Packages)

5 context 官方文档说明 CancelFunc 的作用,以及未调用 CancelFunc 会泄漏子 context 及其子节点;Done() 返回的 channel 会在取消时关闭。(Go Packages)

6 sync.WaitGroup 官方文档说明其计数、AddDoneWait 的语义,以及计数器为负数会 panic。(Go Packages)

7 Go 官方 Data Race Detector 文档定义了 data race,并说明可用 go test -racego run -racego build -race 检测;报告包含冲突访问堆栈与 goroutine 创建堆栈。(Go)

8 go vet 官方文档说明其会检查 Go 源码中的可疑结构;相关 analyzer 包括 waitgroupcopylocksloopclosurelostcancel。(Go Packages)

9 Go 语言规范说明 channel 的阻塞、nil channel、closed channel、send、receive、close 等语义。(Go)

[10] Go runtime 源码与运行时文档包含 all goroutines are asleep - deadlock!GOTRACEBACKdebug.SetTracebackSIGQUIT stack dump 等诊断依据。(Go)

[11] Go 语言规范说明 panic 后当前函数执行停止,defer 按后进先出执行,panic 会沿调用栈传播,直到被 recover 或导致程序终止。(Go)

GitHub Discussions

参与讨论

评论会同步到 stellhub/stell-web 仓库的 GitHub Discussions。

Powered by VitePress and GitHub Discussions.