for range 可以遍历 切片、map等。
for range 遍历时会首先计算切片的长度,然后创建每个元素的副本。而不是直接返回对该元素的引用,因此如果使用该值变量的地址作为指向每个元素的指针,就会造成错误。
在for range中,
每次循环,循环变量index和value都会被重新赋值(值拷贝,因此只是副本)。如果在循环体中启动协程并发,很可能整个循环结束了协程才开始执行,此时所有协程使用的循环变量就有可能已被改写;如果在循环体中遍历指针类型,最终实际指向的可能都是同一个地址;
可以通过临时变量方式或参数传参方式解决。
存在问题的循环体并发:
package main
import (
"fmt"
"golang.org/x/sync/errgroup"
)
func main() {
group := new(errgroup.Group)
nums := []int{1, 2, 3,4}
for _, num := range nums {
group.Go(func() error {
fmt.Println("Num:",num)
return nil
})
}
// 捕获err
if err := group.Wait(); err != nil {
fmt.Println("Get errors: ", err)
}else {
fmt.Println("Get all num successfully!")
}
}
解决方式一:临时变量
package main
import (
"fmt"
"golang.org/x/sync/errgroup"
)
func main() {
group := new(errgroup.Group)
nums := []int{1, 2, 3,4}
for _, num := range nums {
tmpNum:=num
group.Go(func() error {
fmt.Println("Num:",tmpNum)
return nil
})
}
// 捕获err
if err := group.Wait(); err != nil {
fmt.Println("Get errors: ", err)
}else {
fmt.Println("Get all num successfully!")
}
}
解决方式二:参数传参
errgroup不支持协程传参,因此使用了goroutine
package main
import (
"fmt"
"time"
)
func main() {
nums := []int{1, 2, 3, 4}
for _, num := range nums {
go func(paramNum int) {
fmt.Println("Num:", paramNum)
return
}(num)
}
time.Sleep(2 * time.Second)
}