根本原因是默认 HTTP 客户端连接池限制:MaxIdleConnsPerHost 默认为2,导致同一域名请求排队;需调大该值并用独立http.Client,分片下载+Range头避免并发写冲突,配合context超时防卡死。
为什么 go 启动的 goroutine 无法并发下载文件?
常见现象是:写了多个 go downloadFile(url, dst),但实际下载速度没提升,甚至变慢。根本原因不是 goroutine 没起作用,而是默认的 HTTP 客户端复用底层 TCP 连接池(http.DefaultTransport),而它的 MaxIdleConnsPerHost 默认值是 2 —— 意味着同一域名最多只保持 2 个空闲连接,后续请求排队等待。
- 把
http.DefaultTransport的MaxIdleConnsPerHost设为足够大的数(如 100) - 显式创建独立的
http.Client,避免被其他逻辑意外修改全局 transport - 注意:不是越大越好,超过目标服务器承受能力反而触发限流或拒绝连接
如何安全地并发写入同一个文件?
多个 goroutine 直接 f.Write() 同一个 *os.File 会导致数据错乱或 panic —— 文件描述符是共享资源,Write() 不是原子操作。正确做法是按分片下载、再合并,或用同步机制隔离写入。
- 推荐分片下载:用
Range请求头指定字节区间(如"bytes=0-1048575"),每个 goroutine 下载一段,最后用os.OpenFile(..., os.O_WRONLY|os.O_CREATE, 0644)随机写入对应位置 - 若必须顺序追加(如日志类场景),用
sync.Mutex包裹f.Write(),但会严重削弱并发收益 - 切勿用
fmt.Fprintln(f, ...)并发写,它隐含格式化+换行,竞争更隐蔽
io.Copy 在并发场景下为什么容易卡住?
io.Copy(dst, src) 是阻塞调用,如果某个下载链接响应极慢或挂起,对应 goroutine 就一直卡在那儿,既不超时也不释放资源,拖垮整个下载队列。