在Go并发编程中,我们经常会将运行的中间数据放到 chan 里面,然后启动另外一个协程从chan 里面获取数据,如下所示:
package main
import (
"fmt"
"sync"
)
type A struct {
id int
}
func main() {
channel := make(chan A, 5)
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
for a := range channel { // 读 chan
wg.Add(1)
go func() {
defer wg.Done()
fmt.Println(a.id)
}()
}
}()
for i := 0; i < 10; i++ {
channel <- A{id:i} // 将数据放到chan
}
close(channel)
wg.Wait()
}
我们希望得到的结果打印从 0 到 9 这几个数字,但真实的结果却是
6
6
6
6
6
9
9
9
9
9
这是因为go的for range 的遍历中间变量 a 其实是共享同一个地址空间的,也就是说每次for循环遍历的时候他们读的是同一个变量。所以才会导致上面的现象。解决这个问题的方法就是复制这个变量的值,譬如
go func() {
defer wg.Done()
for a := range channel {
wg.Add(1)
go func(item A) {
defer wg.Done()
fmt.Println(item.id)
}(a) // 变量复制
}
}()
我们还可以go 协程之外去捕获复制这个变量也是可以的。
for a := range channel {
wg.Add(1)
item := a // 变量复制
go func() {
defer wg.Done()
fmt.Println(item.id)
}()
}