
在 go 并发应用中,应始终复用同一个 *http.client 实例来发起 https 请求,因其天然支持 goroutine 安全、连接池复用和并发执行,无需为每个 goroutine 创建独立客户端。
在 go 并发应用中,应始终复用同一个 *http.client 实例来发起 https 请求,因其天然支持 goroutine 安全、连接池复用和并发执行,无需为每个 goroutine 创建独立客户端。
Go 标准库中的 *http.Client 是完全并发安全的(goroutine-safe)。官方文档明确声明:“Clients are safe for concurrent use by multiple goroutines.” 这意味着你可以在任意数量的 goroutine 中共享并同时调用同一个 client.Do()、client.Get() 或 client.Post() 方法,底层会自动协调请求调度、复用 TCP 连接(通过内置的 http.Transport 连接池),并避免串行阻塞。
✅ 正确做法(推荐):
// 全局或包级复用单个 client —— 高效且线程安全
var httpClient = &http.Client{
Timeout: 10 * time.Second,
Transport: &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 100,
IdleConnTimeout: 30 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
},
}
func fetchAPI(url string, ch chan<- string) {
resp, err := httpClient.Get(url)
if err != nil {
ch <- fmt.Sprintf("error for %s: %v", url, err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
ch <- fmt.Sprintf("success for %s: %d bytes", url, len(body))
}
// 启动多个并发请求
urls := []string{"https://httpbin.org/delay/1", "https://httpbin.org/delay/2"}
ch := make(chan string, len(urls))
for _, u := range urls {
go fetchAPI(u, ch)
}
for i := 0; i < len(urls); i++ {
fmt.Println(<-ch)
}⚠️ 注意事项:
- 不要为每个 goroutine new 一个 client:这将导致连接池失效、文件描述符耗尽、TLS 握手开销剧增;
- 自定义 Transport 时务必复用:若需配置超时、重试、连接池等,应在初始化 client 时统一设置,而非每次请求新建;
- 避免滥用 DefaultClient:虽它也是并发安全的,但其 Transport 默认配置较保守(如 MaxIdleConns=100),生产环境建议显式构造并调优;
- 注意上下文传播:高并发场景下,务必使用 client.Do(req.WithContext(ctx)) 支持请求级超时与取消,防止 goroutine 泄漏。
总结:Go 的 http.Client 是为高并发而生的设计典范——共享即安全、复用即高效。坚持“一个 client,多 goroutine 共用”原则,是构建高性能 HTTP 客户端服务的基石。


















