goroutine并发需先全量启动再统一等待:wg.add(1)在go前、wg.wait()在所有go后且仅一次;循环变量需传参避免捕获最后值;channel缓冲大小应匹配任务数,推荐res := make(chan string, len(urls))。

goroutine 启动后立即 wg.Wait() 会导致伪并发
这是最常踩的坑:循环里每启动一个 go fetch() 就立刻调用 wg.Wait(),结果所有请求仍是串行执行,还多花了 goroutine 调度开销。
真正并发必须「先全量启动,再统一等待」:
-
wg.Add(1)放在go前,确保计数准确 -
wg.Wait()必须在所有go语句执行完之后、且仅调用一次 - 闭包中直接引用循环变量(如
url)会捕获最后一次值,要用参数传入:go func(u string) { ... }(url)
channel 缓冲大小不匹配 task 数量会阻塞或 panic
比如要处理 10 个 URL,却声明 make(chan string, 5),前 5 个 goroutine 写入后就会卡住;若声明 make(chan string, 0)(无缓冲),则每个 goroutine 都得等接收方读取才能继续,容易死锁。
推荐做法:
- 聚合场景下,用带缓冲 channel,容量设为任务总数:
res := make(chan string, len(urls)) - 写入侧不用
defer,直接res - 读取侧用
for range res,channel 关闭后自动退出 - 务必在所有 goroutine 启动完毕后
close(res),否则range永不结束
http.Get 不关 resp.Body 会导致文件描述符耗尽
高并发下不 defer resp.Body.Close(),几十个请求就可能触发 too many open files 错误,服务直接不可用。
Go语言(Golang)1.26.0版本提供 Go 官方 Windows amd64 MSI 安装包下载入口,版本号 1.26.0,可用于旧项目维护、兼容性测试和指定版本开发环境配置。
注意点:
-
resp.Body.Close()必须在所有ReadAll或流式读取完成后调用 - 如果
err != nil,resp可能为nil,需判空:if resp != nil { defer resp.Body.Close() } - 不要在 goroutine 外提前关闭 body,那是另一个 goroutine 的资源
不加限流直接起 200 个 goroutine 很可能被目标服务拒绝
本地测试时看似快,上线后触发对方风控、连接重置或 429,反而比串行还慢。
简单可控的限流方案:
- 用带缓冲 channel 当信号量:
sem := make(chan struct{}, 20) - 每个 goroutine 先
sem ,结束后 <code> - 或者改用协程池(如
worker pool),避免无序创建海量 goroutine - HTTP 客户端层面也可设
http.DefaultClient.Transport.MaxIdleConnsPerHost = 100
并发不是越多越好,真实瓶颈常在 DNS 解析、连接建立、对方限速或本机 fd 上限——这些地方漏掉,光堆 goroutine 没用。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










