不能无限制起goroutine,因每秒10万请求启10万个goroutine会导致调度器卡顿、内存暴涨(10万×2KB起)、GC压力陡增;虽初始栈仅2KB,但需占用调度队列、存在栈动态扩容开销,且受G-P-M模型中P数量限制,常见现象为runtime: gp.sp异常。

为什么不能无限制起 goroutine
每秒 10 万请求就起 10 万 goroutine,调度器会卡住,内存暴涨,GC 压力陡增。goroutine 虽轻量(初始栈 2KB),但不是免费的——它要占调度队列、有栈扩容开销、受 G-P-M 模型中 P 数量限制。
常见错误现象:runtime: gp.sp (栈溢出)、<code>gc controller: cycle not completed(GC 长时间无法完成)、P99 响应时间毛刺飙升。
- IO 密集型服务:并发数建议设为
runtime.NumCPU() * 5 ~ 10 - CPU 密集型服务:并发数建议 ≤
runtime.NumCPU() * 2,避免线程争抢 - 永远不要在 HTTP handler 里直接写
go handleXXX(c),除非你明确控制了总数
用 ants/v2 做协程池比原生 go 更稳
ants 不是“锦上添花”,而是生产环境防崩底牌。它把 goroutine 生命周期收口到池里,支持任务超时、拒绝策略、运行中动态调参,原生 go 关键字做不到这些。
使用场景:高频短任务(如日志打点、Redis 查询、轻量校验);突发流量下需限流保核心链路。
立即学习“go语言免费学习笔记(深入)”;
- 初始化必须在
init()或main()开头,避免竞态 - 提交任务用
pool.Submit(func()),别传闭包捕获*gin.Context等大对象 - 拒绝策略选
ants.Discard而非ants.CallerRuns,防止 handler 线程被拖慢
WaitGroup + Channel 组合比纯 channel 更可控
只靠 chan 关闭来通知结束,容易漏掉未消费完的数据或 goroutine 泄漏;只用 sync.WaitGroup 又没法做任务级超时。两者配合才是真实业务中的常用解法。
典型错误:在循环里反复 wg.Add(1) 后才启动 goroutine,导致 Add 和 go 不在同一个原子上下文中,引发 panic。
-
wg.Add()必须在go之前调用,且不能放在 goroutine 内部 - channel 建议带缓冲(如
make(chan Result, len(tasks))),避免 worker 因发送阻塞而卡死 - 用
select { case ch 替代无条件发送,防止单个任务拖垮整组
Context 传播必须贯穿整个调用链
HTTP 请求进来时的 context.WithTimeout,如果只传给 DB 查询,没传给 Redis、HTTP client、甚至子 goroutine,那超时就形同虚设。goroutine 一旦启动,就脱离父 context 控制范围。
容易踩的坑:用 context.Background() 或 context.TODO() 替代传入的 c.Request.Context();在 goroutine 里新建 context 而不继承;忘记用 ctx.Done() 监听取消信号。
- 所有外部调用(
http.Client.Do、redis.Client.Get、sql.DB.Query)必须显式传 ctx - 自定义 goroutine 启动前,用
ctx, cancel := context.WithTimeout(parentCtx, time.Second*3)包一层 - channel 接收务必配合
select { case v :=
真正难的不是写对一个 goroutine,而是让成百上千个 goroutine 在不同生命周期、不同资源依赖、不同失败路径下,全部能被及时回收、统一受控、可观测可中断。这需要从第一行 go 开始就带着 context 和池意识写,而不是等压测崩了再补。


















