Fiber.App 无法限制 goroutine 数量,需业务层加信号量或用 errgroup.Group + context;最常用方案是 chan struct{} 轻量信号量,如 sem := make(chan struct{}, 5),进 handler 前抢令牌、执行后归还。

直接用 fiber.App 自带的并发控制机制无法限制 goroutine 数量——它不提供内置信号量或 worker 池,所有中间件和 handler 都是裸 go 启动的。真正可控、可落地的方式只有两种:在业务层加信号量,或用 errgroup.Group + context 统一收口。
用 chan struct{} 做轻量信号量(最常用)
这是 Fiber 项目里最直接、无依赖、runtime 安全的方案。核心就是初始化一个带缓冲的 channel,在进 handler 前“抢令牌”,执行完再归还。
-
sem := make(chan struct{}, 5)表示最多 5 个并发任务同时执行;别用chan int,避免误当计数器用 - 必须在 handler 内部写入:
sem ,若缓冲满则阻塞,天然限流 - 释放必须用
defer func() { ,否则 panic 或提前 return 会导致令牌漏还 - 不要在中间件里全局拦截——Fiber 的中间件是串行执行的,goroutine 是在 handler 里才真正 spawn 出来
示例片段:
sem := make(chan struct{}, 10)
app.Get("/fetch", func(c *fiber.Ctx) error {
sem // 实际 HTTP 请求或 DB 查询
resp, _ := http.DefaultClient.Get("https://api.example.com/data")
defer resp.Body.Close()
return c.JSON(resp)
})
用 errgroup.Group 替代裸 go(推荐用于多子任务场景)
当你在单个请求里要并发发起多个下游调用(比如查 3 个微服务),裸 go + sync.WaitGroup 很容易漏 cancel 或 panic 后卡死。用 errgroup 能自动传播 context 取消和第一个错误。
- 必须传入 request context:
g, ctx := errgroup.WithContext(c.Context()) - 每个子 goroutine 必须检查
ctx.Err(),尤其在阻塞点(如http.Do、time.Sleep、channel receive)前加select - 不要把整个 handler 包进
errgroup——那是错的;它只适用于“一个请求内启多个子任务”的场景 - 如果下游没有超时控制,
errgroup也救不了你;务必给http.Client设Timeout或用context.WithTimeout
示例片段:
app.Get("/batch", func(c *fiber.Ctx) error {
g, ctx := errgroup.WithContext(c.Context())
<pre class="brush:php;toolbar:false;">var resA, resB string
g.Go(func() error {
req, _ := http.NewRequestWithContext(ctx, "GET", "https://a.example.com", nil)
resp, err := http.DefaultClient.Do(req)
if err != nil { return err }
defer resp.Body.Close()
resA = "ok"
return nil
})
g.Go(func() error {
select {
case <p>})
</p>别碰 runtime.GOMAXPROCS 和中间件级“全局限流”
这两个是 Fiber 开发者最容易踩的坑。
-
runtime.GOMAXPROCS(1)不会减少 goroutine 总数,只会让 10 万个 goroutine 在单个 P 上排队切换——CPU 利用率可能更低,但调度延迟更高,且完全不影响内存和 fd 消耗 - 试图在 Fiber 中间件里用
sync.Mutex或atomic统计并发请求数,本质是伪限流:它只能粗略挡掉部分请求,但无法防止 handler 内部再开 goroutine,也无法解决 I/O 阻塞导致的堆积 - 容器环境(K8s/Docker)下
runtime.NumCPU()返回的是宿主机核数,不是 cgroups 限额;若没显式设置GOMAXPROCS,可能意外跑满物理核
真正关键的点往往被忽略:goroutine 泄漏不来自并发数设得太高,而来自没处理好 context 生命周期、没关闭 channel、或在 defer 里忘了释放资源。上线后盯紧 runtime.NumGoroutine() 的增长趋势,比硬编码一个 10 更重要。











