

新闻资讯
技术学院必须用 sync.WaitGroup 显式跟踪并发请求生命周期,wg.Add(1)需在goroutine启动前调用,wg.Done()建议defer调用;每个请求需独立context.WithTimeout防止单点拖垮整体;结果应通过带缓冲channel(容量=len(urls))安全汇总。
sync.WaitGroup 控制并发请求生命周期直接起一堆 goroutine 而不等待完成,会导致主函数提前退出、结果丢失。必须用 sync.WaitGroup 显式跟踪所有请求是否结束。
常见错误是忘记调用 wg.Add(1) 或在 goroutine 外调用 wg.Done(),导致死锁或 panic。
wg.Add(1) 必须在启动 goroutine 前调用(不能放在 goroutine 内部)wg.Done() 必须在每个 goroutine 结束前调用,建议用 defer wg.Done()
wg.Wait() 会阻塞,直到所有子 goroutine 完成context.WithTimeout 防止单个请求拖垮整体并发请求中某个 URL 响应慢或挂死,会卡住整个 wg.Wait(),必须为每个请求单独设置超时。
不能只给 http.Client 设置全局 Timeout,那会影响所有请求;也不能复用同一个 context.Context,否则一个超时会 cancel 所有请求。
立即学习“go语言免费学习笔记(深入)”;
context.WithTimeout(ctx, 5*time.Second) 创建独立子 contexthttp.NewRequestWithContext(),而非 http.Get()
cancel()(用 defer cancel() 最安全)多个 goroutine 同时写入一个 slice 会引发 data race,用 channel 中转最自然。但无缓冲 channel 可能阻塞 goroutine,影响并发吞吐。
缓冲大小设为请求数量可避免阻塞,也防止结果丢失(即使主 goroutine 还没开始读)。
results := make(chan *Result, len(urls))
results
wg.Wait() 后循环接收:for i := 0; i
package mainimport ( "context" "fmt" "net/http" "sync" "time" )
type Result struct { URL string StatusCode int Err error }
func main() { urls := []string{ "https://www./link/5f69e19efaba426d62faeab93c308f5c", "https://www./link/98a733901e53052474f2320d0a3a9473", "https://www./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: %v\n", r.URL, r.Err) } else { fmt.Printf("OK %s: %d\n", r.URL, r.StatusCode) } }}
注意
http.DefaultClient本身是并发安全的,但它的Transport默认连接池有限(MaxIdleConnsPerHost=100),高并发时不用额外配置;真正容易被忽略的是:每个resp.Body必须关闭,否则连接不会释放,后续请求可能卡住。