goroutine 循环变量捕获错误、sync.waitgroup 使用不当、channel 收发不匹配是三大常见并发陷阱:循环中未传参导致所有 goroutine 读取同一变量值;wg.add 必须在启动前调用,wg.done 在 goroutine 内执行;向无缓冲 channel 发送或从空 channel 接收会阻塞。

goroutine 启动时变量捕获容易出错
直接在 for 循环里用 go func() { ... }() 启动 goroutine,但没把循环变量传进去,结果所有 goroutine 都读到同一个值——这是新手最常踩的坑。
比如遍历 URL 列表并发请求,写成这样会全部抓同一个地址:
for _, url := range urls {
go func() {
fetch(url) // ❌ url 是循环外的闭包变量,最终值是最后一次迭代的值
}()
}
正确做法是显式传参:
- 用
go func(u string) { fetch(u) }(url) - 或在循环内定义新变量:
u := url; go func() { fetch(u) }()
用 sync.WaitGroup 等待所有 goroutine 完成
不等就直接退出,main 函数结束,所有 goroutine 被强制终止——哪怕它们还在 HTTP 请求中。
关键点:
- 必须在启动 goroutine 前调用
wg.Add(1),不能放在 goroutine 里 -
wg.Done()必须在 goroutine 内部执行(通常 defer 或结尾) - 主 goroutine 用
wg.Wait()阻塞,直到全部完成
漏掉 Add 或多调一次 Done 都会导致死锁或 panic。
channel 收发不匹配会卡死
往未缓冲的 channel 发送数据,但没人接收;或者从空 channel 接收,但没人发送——程序就停在这儿不动了。
典型场景:
- 结果 channel 没做缓冲,又没开足够 goroutine 去读,
ch 就永远阻塞 - 用
for range ch读 channel,但忘了close(ch),循环永不退出 - 多个 goroutine 往同一 channel 写,但只用一个 goroutine 读,容易丢数据或阻塞
建议:结果 channel 用带缓冲的 make(chan string, len(urls)),避免同步耦合。
HTTP 请求没设超时,整个并发系统可能挂住
一个慢响应或无响应的 URL,会让对应 goroutine 卡死几秒甚至几分钟,拖垮整体吞吐。
必须为每个请求加 context 控制:
- 用
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - 传给
http.NewRequestWithContext(ctx, ...) - 记得
defer cancel()防止 goroutine 泄漏
没超时的并发爬虫,在真实网络环境下基本不可用——不是慢,是彻底不可控。
实际写并发抓取时,最麻烦的从来不是启动 goroutine,而是让它们「安全地停下来」:超时、错误、关闭信号、channel 关闭顺序、WaitGroup 计数一致性……这些细节一错,程序就表现诡异。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











