使用 sync.waitgroup 可安全并发 http 请求并同步完成:需预调 wg.add(1)、defer wg.done()、wg.wait() 等待;共享结果用预分配切片或 mutex 保护;统一用 context.withtimeout 控制总超时;须检查状态码、解析错误并关闭 resp.body。

用 sync.WaitGroup + goroutine 并发请求但不阻塞主流程
并发发多个 HTTP 请求本身不难,难点在于等所有结果回来、统一处理,又不能让某个慢接口拖垮整体。直接用 go 启动一堆协程后,必须靠 sync.WaitGroup 来同步完成信号——不是靠 sleep 猜时间,也不是靠 channel 盲等。
常见错误是漏掉 wg.Add(1) 或在 goroutine 外调用 wg.Done(),导致主 goroutine 提前退出或死锁。
-
wg.Add(1)必须在go语句前调用,且和启动的 goroutine 数量严格一致 - 每个 goroutine 结束前必须调用
wg.Done(),建议用defer wg.Done()防遗漏 - 主 goroutine 调用
wg.Wait()会阻塞,但它只等“完成通知”,不关心返回值怎么存
把响应数据安全写入共享切片:用 mutex 还是预分配+索引?
多个 goroutine 往同一个 []interface{} 或 []*http.Response 写数据,不加保护必 panic:slice 是非线程安全的,append 可能触发底层数组扩容,引发并发写冲突。
两种稳妥做法:
在 Golang 中使用 samber/hot 进行内存缓存,支持 LRU、LFU、TinyLFU、W‑TinyLFU、S3FIFO、ARC、TwoQueue、SIEVE、FIFO 等淘汰算法,提供 TTL、缓存加载器及分片功能。
- 预分配好切片(长度 = 请求个数),用请求顺序索引直接赋值:
results[i] = resp,完全规避锁和竞争 - 如果顺序不重要或无法预知数量,用
sync.Mutex包裹append操作;别用sync.RWMutex,写多读少场景下它反而更重 - 避免把
*http.Response.Body直接存进共享结构——它是一次性 reader,后续读取会失败;要立刻用io.ReadAll拿到字节再存
context.WithTimeout 不只是防卡死,更是控制整组请求生命周期
单个请求超时用 http.Client.Timeout,但聚合场景下更关键的是「整组请求的总耗时上限」。比如你发 5 个接口,希望 3 秒内全拿到结果,不管其中某个是否已耗时 2.8 秒——这时必须用 context.WithTimeout 统一传给所有请求。
- 每个
http.NewRequestWithContext都要传同一个ctx,否则超时互不影响 - 不要在 goroutine 里重新
context.WithTimeout,那会创建孤立子上下文,父 ctx 取消时它收不到信号 - 如果某个请求提前返回 error 是
context.DeadlineExceeded,说明整组已被取消,其他还在跑的请求也会陆续收到 cancel 信号
错误处理别只看 err != nil,HTTP 状态码和 body 解析失败同样算失败
并发请求中,一个 err == nil 只代表 TCP 连通、TLS 握手成功、收到了响应头,不代表业务成功。常见坑是没检查 resp.StatusCode,或者直接 json.Unmarshal(resp.Body, &v) 却忽略解码错误。
- 把状态码判断、body 读取、JSON 解析三步都包进每个 goroutine 的错误分支里
- 别把解析失败的 error 吞掉或转成
nil,否则聚合后你看到的可能是 “5 个请求都成功了”,实际 3 个返回了 500 或空 body - 建议为每个请求定义结构体字段如
Err error、Data interface{}、StatusCode int,统一收口错误类型
最易被忽略的是:所有 resp.Body 必须被关闭,哪怕你已经 io.ReadAll 过。不关会导致连接不复用、fd 耗尽。用 defer resp.Body.Close() 放在每个 goroutine 最开头就行。
大量免费API接口:立即使用
涵盖生活服务API、金融科技API、企业工商API、等相关的API接口服务。免费API接口可安全、合规地连接上下游,为数据API应用能力赋能!










