goroutine 启动下载任务没效果是因为主 goroutine 过早退出,需用 sync.WaitGroup 等待;并发过多易触发 429 或资源耗尽,应通过带缓冲 channel(如 sem := make(chan struct{}, 10))限流。
goroutine 启动下载任务时为什么没效果?
常见现象是写了 go downloadFile(url) 却发现所有请求串行发出,甚至只完成第一个。根本原因通常是主 goroutine 过早退出——Go 程序不会等待未显式同步的子 goroutine 结束。
必须用 sync.WaitGroup 或 channel 控制生命周期:
var wg sync.WaitGroup
for _, url := range urls {
wg.Add(1)
go func(u string) {
defer wg.Done()
downloadFile(u) // 实际下载逻辑
}(url)
}
wg.Wait() // 阻塞直到全部完成
- 切记传参用
(url)而非(urls[i])闭包陷阱,否则所有 goroutine 可能共享最后一个url值 - 不要在循环里直接用
go downloadFile(url)+defer wg.Done(),因为defer在函数返回时才执行,而匿名函数已返回,Done()永远不调用
如何限制并发数避免被封或压垮服务?
无节制启 goroutine(比如 1000 个)会耗尽本地文件描述符、触发 HTTP 连接池瓶颈,或让目标服务器返回 429 Too Many Requests。需用带缓冲的 channel 做信号量控制:
sem := make(chan struct{}, 10) // 最多 10 个并发
for _, url := range urls {
sem <- struct{}{} // 获取令牌
go func(u string) {
defer func() { <-sem }() // 归还令牌
downloadFile(u)
}(url)
}
// 等待所有 goroutine 启动后,再等它们结束(需配合 WaitGroup)
- 缓冲大小不是越大越好:
http.DefaultClient默认只保持 100 个空闲连接,MaxIdleConnsPerHost默认 2,建议设为 5–20 之间并实测 - 别把
sem和WaitGroup混用逻辑:前者控并发,后者控完成,两者通常共存
downloadFile 函数里哪些地方容易阻塞主线程?
看似简单的 http.Get 其实暗藏多个阻塞点:DNS 解析、TCP 握手、TLS 握手、响应体读取。任一环节超时都会卡住整个 goroutine。