为何在迭代过程中频繁启用协程(Goroutine)会成为编程禁忌?
- 内容介绍
- 文章标签
- 相关推荐
本文共计779个文字,预计阅读时间需要4分钟。
gorange 变量被封闭,导致所有 goroutine 读取的是同一地址上的最终值。这并非不要用,而是不加处理就直接用,几乎必然出错。
为什么 for range 中直接起 goroutine 会打印错数据?
因为 Go 的 for 循环复用迭代变量(如 i 或 item),它在每次迭代中不创建新变量,只是更新同一块内存。而 goroutine 启动是异步的,往往等不到循环体执行完就已调度运行,此时变量早已变成最后一次迭代的值。
常见错误现象:
for i := 0; i 可能输出 <code>3 3 3,而非0 1 2- 爬虫中
for _, item := range items { go func() { send(item) }() }导致所有协程发的都是最后一个item
GoLand 会直接标红警告:Loop variable 'item' captured by func literal。
怎么传参才能让每个 goroutine 拿到正确的值?
核心原则:让值在启动 goroutine 时就被求值并绑定,而不是靠引用外部变量。
两种可靠写法:
- 显式传参:
go func(i int) { fmt.Println(i) }(i)——i在调用时被拷贝进新协程栈 - 局部变量赋值:
i0 := i; go func() { fmt.Println(i0) }()—— 创建独立变量,避免复用
注意:不能写成 go func() { i0 := i; fmt.Println(i0) }(),这没用,因为 i0 仍捕获的是外部 i。
sync.WaitGroup 和 defer wg.Done() 必须配对出现
协程里漏掉 wg.Done() 是静默死锁的高发区,尤其在有提前返回逻辑时。
典型错误场景:
-
if err != nil { return }前没调用wg.Done(),导致wg.Wait()永远阻塞 - panic 后
wg.Done()没执行,同样卡住主协程
正确姿势只有一条:defer wg.Done() 必须放在 goroutine 函数体最开头,且不能被任何条件分支绕过。
别忽略协程生命周期和资源累积
无节制启协程,不设退出机制,等于在堆上不断 malloc 小对象 —— 看似轻量,实则积少成多。
容易被忽略的点:
-
for range启 1000 个协程,但没做并发控制,可能瞬间打爆连接数或内存 - 协程内含
time.Sleep或等待 channel,却没配context.WithTimeout,导致 goroutine 泄漏 - 忘记用
runtime.Gosched()或合理分批,CPU 被单核占满,调度器失衡
真正安全的做法不是“少用协程”,而是“每个协程都明确它的起点、终点和退出路径”。
本文共计779个文字,预计阅读时间需要4分钟。
gorange 变量被封闭,导致所有 goroutine 读取的是同一地址上的最终值。这并非不要用,而是不加处理就直接用,几乎必然出错。
为什么 for range 中直接起 goroutine 会打印错数据?
因为 Go 的 for 循环复用迭代变量(如 i 或 item),它在每次迭代中不创建新变量,只是更新同一块内存。而 goroutine 启动是异步的,往往等不到循环体执行完就已调度运行,此时变量早已变成最后一次迭代的值。
常见错误现象:
for i := 0; i 可能输出 <code>3 3 3,而非0 1 2- 爬虫中
for _, item := range items { go func() { send(item) }() }导致所有协程发的都是最后一个item
GoLand 会直接标红警告:Loop variable 'item' captured by func literal。
怎么传参才能让每个 goroutine 拿到正确的值?
核心原则:让值在启动 goroutine 时就被求值并绑定,而不是靠引用外部变量。
两种可靠写法:
- 显式传参:
go func(i int) { fmt.Println(i) }(i)——i在调用时被拷贝进新协程栈 - 局部变量赋值:
i0 := i; go func() { fmt.Println(i0) }()—— 创建独立变量,避免复用
注意:不能写成 go func() { i0 := i; fmt.Println(i0) }(),这没用,因为 i0 仍捕获的是外部 i。
sync.WaitGroup 和 defer wg.Done() 必须配对出现
协程里漏掉 wg.Done() 是静默死锁的高发区,尤其在有提前返回逻辑时。
典型错误场景:
-
if err != nil { return }前没调用wg.Done(),导致wg.Wait()永远阻塞 - panic 后
wg.Done()没执行,同样卡住主协程
正确姿势只有一条:defer wg.Done() 必须放在 goroutine 函数体最开头,且不能被任何条件分支绕过。
别忽略协程生命周期和资源累积
无节制启协程,不设退出机制,等于在堆上不断 malloc 小对象 —— 看似轻量,实则积少成多。
容易被忽略的点:
-
for range启 1000 个协程,但没做并发控制,可能瞬间打爆连接数或内存 - 协程内含
time.Sleep或等待 channel,却没配context.WithTimeout,导致 goroutine 泄漏 - 忘记用
runtime.Gosched()或合理分批,CPU 被单核占满,调度器失衡
真正安全的做法不是“少用协程”,而是“每个协程都明确它的起点、终点和退出路径”。

