如何在Golang中实现并发API请求_Golang HTTP并发调用与结果汇总方法

17次阅读

必须用 sync.WaitGroup 显式跟踪并发请求生命周期,wg.Add(1)需在goroutine启动前调用,wg.Done()建议defer调用;每个请求需独立context.WithTimeout防止单点拖垮整体;结果应通过带缓冲channel(容量=len(urls))安全汇总。

如何在Golang中实现并发API请求_Golang HTTP并发调用与结果汇总方法

sync.WaitGroup 控制并发请求生命周期

直接起一 goroutine 而不等待完成,会导致主函数提前退出、结果丢失。必须用 sync.WaitGroup 显式跟踪所有请求是否结束。

常见错误是忘记调用 wg.Add(1) 或在 goroutine 外调用 wg.Done(),导致死锁或 panic。

  • wg.Add(1) 必须在启动 goroutine 前调用(不能放在 goroutine 内部)
  • wg.Done() 必须在每个 goroutine 结束前调用,建议用 defer wg.Done()
  • 主 goroutine 调用 wg.Wait() 会阻塞,直到所有子 goroutine 完成

context.WithTimeout 防止单个请求拖垮整体

并发请求中某个 URL 响应慢或挂死,会卡住整个 wg.Wait(),必须为每个请求单独设置超时。

不能只给 http.Client 设置全局 Timeout,那会影响所有请求;也不能复用同一个 context.Context,否则一个超时会 cancel 所有请求。

立即学习go语言免费学习笔记(深入)”;

  • 对每个请求调用 context.WithTimeout(ctx, 5*time.Second) 创建独立子 context
  • 把子 context 传给 http.NewRequestWithContext(),而非 http.Get()
  • 记得调用 cancel()(用 defer cancel() 最安全)

用带缓冲的 channel 汇总结果并避免竞态

多个 goroutine 同时写入一个 slice 会引发 data race,用 channel 中转最自然。但无缓冲 channel 可能阻塞 goroutine,影响并发吞吐。

缓冲大小设为请求数量可避免阻塞,也防止结果丢失(即使主 goroutine 还没开始读)。

  • 声明 results := make(chan *Result, len(urls))
  • 每个 goroutine 写入:results
  • 主 goroutine 在 wg.Wait() 后循环接收:for i := 0; i

完整示例:并发请求 3 个 URL 并汇总状态码

package main 

import ( "context" "fmt" "net/http" "sync" "time" )

type Result struct { URL string StatusCode int Err error }

func main() { urls := []string{ "https://www.php.cn/link/5f69e19efaba426d62faeab93c308f5c", "https://www.php.cn/link/98a733901e53052474f2320d0a3a9473", "https://www.php.cn/link/874b2add857bd9bcc60635a51eb2b697", }

results := make(chan *Result, len(urls)) var wg sync.WaitGroup ctx := context.Background()  for _, url := range urls {     wg.Add(1)     go func(u string) {         defer wg.Done()         reqCtx, cancel := context.WithTimeout(ctx, 3*time.Second)         defer cancel()          req, err := http.NewRequestWithContext(reqCtx, "GET", u, nil)         if err != nil {             results <- &Result{URL: u, Err: err}             return         }          resp, err := http.DefaultClient.Do(req)         if err != nil {             results <- &Result{URL: u, Err: err}             return         }         defer resp.Body.Close()          results <- &Result{URL: u, StatusCode: resp.StatusCode}     }(url) }  wg.Wait() close(results)  for r := range results {     if r.Err != nil {         fmt.Printf("ERROR %s: %vn", r.URL, r.Err)     } else {         fmt.Printf("OK %s: %dn", r.URL, r.StatusCode)     } }

}

注意 http.DefaultClient 本身是并发安全的,但它的 Transport 默认连接池有限(MaxIdleConnsPerHost=100),高并发时不用额外配置;真正容易被忽略的是:每个 resp.Body 必须关闭,否则连接不会释放,后续请求可能卡住。

text=ZqhQzanResources