盲目加 goroutine 不仅不提速,反而导致 NumGoroutine 暴涨、GC pause 拉长、HTTP 超时增多;应通过带缓冲 channel 控制并发数量、复用资源、切断阻塞链。

盲目加 go 不仅不提速,反而会让 runtime.NumGoroutine() 暴涨、GC pause 拉长、HTTP 超时变多——真正有效的优化,是控制数量、复用资源、切断阻塞链。
goroutine 数量失控的典型表现
你看到这些现象,基本可以断定是 goroutine 泄漏或滥用:
-
runtime.NumGoroutine()持续上涨,重启后归零但几小时内又破万 - pprof 查
/debug/pprof/goroutine?debug=2显示大量处于select或chan receive状态的 goroutine - GC 频率明显升高,
runtime.malg在 CPU profile 里占比异常高 - 服务内存 RSS 持续增长,但 heap profile 里对象数没同步涨(说明栈和调度元数据在吃内存)
根本原因不是“协程太重”,而是每创建一个 goroutine,都要走 runtime.newproc 注册、分配初始栈、插入 P 本地队列——高频调用下,这三步叠加比单个 2KB 栈更伤。
用带缓冲 channel 做信号量,别碰 sync.Mutex 控制并发
用 sync.Mutex + 计数器来限并发,本质是把调度压力转嫁成锁竞争,尤其在高 QPS 场景下容易卡住 P。正确做法是用 channel 模拟信号量:
立即学习“go语言免费学习笔记(深入)”;
sem := make(chan struct{}, 10) // 最多 10 个并发
for _, item := range items {
sem <- struct{}{} // 阻塞直到有空位
go func(i Item) {
defer func() { <-sem }() // 必须放 defer,确保释放
process(i)
}(item)
}注意三点:
- 缓冲大小建议设为
2 * runtime.NumCPU()起步,压测后再调;设太小,sender 频繁阻塞;设太大,失去限流意义 - 不要在循环里直接
go process(item)—— 闭包捕获循环变量会导致所有 goroutine 处理同一个item - 如果
process()可能 panic,必须在 goroutine 内部加recover,否则永远不执行,信号量永久泄漏
worker pool 是高频短任务的唯一解
每秒启动上千 goroutine 的 HTTP handler 或消息解析逻辑,必须换成固定 worker 池。裸写 go f() 的瓶颈从来不是栈大小,而是调度注册与 GC 扫描压力。
最小可行实现只需四步:
- 启动固定数量 worker:
for i := 0; i - 任务 channel 必须带缓冲:
taskCh := make(chan func(), 32)(worker 数的 4 倍) - worker 内部用
for-select持续取任务,且必须包recover - 提交任务时只发函数闭包:
taskCh ,别传大结构体(避免逃逸)
别指望 sync.Pool 复用 goroutine——它只能缓存对象,不能缓存协程本身。但它对 bytes.Buffer、JSON 解析器这类高频堆分配对象极有效,Pool.Get() 后务必检查 nil 并重置状态(如 b.Reset())。
context 要贯穿到底,否则超时和取消就是摆设
哪怕用了 worker pool 或信号量,只要 I/O 操作没接 ctx,整个链路就不可中断。常见失效场景:
-
http.Client.Do(req)没换Do(req.WithContext(ctx)) -
db.QueryRow("SELECT ...")没换QueryRowContext(ctx, ...) - 自定义 channel 操作写死
<-ch,没套select { case x := <-ch: ... case <-ctx.Done(): ... }
所有对外依赖必须响应 ctx.Done(),否则一个慢请求会拖住整个 worker,进而让信号量耗尽、新请求排队等待——表面看是并发不足,实则是阻塞未释放。
最易被忽略的一点:goroutine 生命周期和上下文生命周期必须对齐。启动 goroutine 时传入的 ctx,不能是某个 handler 临时生成的、随 request 结束就 cancel 的,而应来自统一的顶层 context(比如 eg.WithContext(ctx) 或自定义 long-lived ctx)。否则 worker 刚启动就被 cancel,任务永远得不到执行。


















