runtime.GOMAXPROCS不能限HTTP并发,因为它只控制并行执行用户代码的P数量,而非goroutine总数;设为1时1000个请求仍排队导致延迟暴涨、连接堆积,需用带缓冲channel或http.Server.ReadTimeout等机制主动限流。

为什么不能靠 runtime.GOMAXPROCS 限 HTTP 并发
它只控制 OS 线程(P)数量,不是 goroutine 并发上限。设成 1 后,1000 个 http.HandlerFunc 仍会排队调度,CPU 可能不飙,但请求延迟暴涨、连接堆积、文件描述符耗尽——问题照旧。
用 http.Server 的 MaxConns 和 MaxIdleConnsPerHost 是错的
这两个字段属于 http.Transport,只影响**出站请求**(比如你的服务调别人 API),对**进站 HTTP 连接处理**完全无效。进站并发由底层 net.Listener 和 handler 启动的 goroutine 数量决定,必须自己控。
最简可靠方案:带缓冲 channel 做信号量
适合大多数内部服务或中低流量网关,无需引入第三方库。核心是把每个请求处理包装成“占坑-干活-还坑”:
sem := make(chan struct{}, 10) // 最多同时处理 10 个请求
<p>http.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
sem </p><pre class="brush:php;toolbar:false;">// 实际业务逻辑,比如 db 查询、HTTP 调用等
time.Sleep(100 * time.Millisecond)
w.WriteHeader(200)})
- 缓冲大小即最大并发数,选值参考:目标后端 QPS × 平均延迟(秒),例如下游 QPS=200、延迟=0.2s → 填
40较稳 - 务必用
defer func() { 归还,别写成 <code>defer (语法错误) - 如果 handler 内部可能 panic,建议包一层
recover,否则信号量卡死
高吞吐/长连接场景该用 worker 池 + sync.WaitGroup
当请求处理时间波动大、或需复用资源(如 DB 连接、解析 buffer)时,固定 worker 池比每请求一个 goroutine 更可控:
jobs := make(chan *http.Request, 100)
for i := 0; i http.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
select {
case jobs
- worker 数量建议从
runtime.NumCPU()开始试,IO 密集型可翻倍,CPU 密集型别超这个数 -
jobs缓冲区大小要大于 worker 数,否则发送端容易阻塞;设太大会吃内存 - 没 close
jobs是最常被忽略的泄漏点——进程无法优雅退出
真正难的不是写几行限流代码,而是判断「这个请求到底卡在哪」:是下游慢?本机 GC 停顿?还是锁竞争?信号量和 worker 池都只是兜底,不是替代 profiling 的手段。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











