如何使用Go的sync/errgroup包编写正确的并发for循环?
解决Go errgroup并发处理切片时的结果异常问题
问题原因分析
你的代码出现异常结果主要有两个核心问题:
- 循环变量捕获陷阱:
for range循环中的i和n是复用的变量,所有goroutine启动后,可能循环已经执行完毕,此时它们捕获到的都是循环最后一轮的i=2和n=2,这就导致output2只有索引2被赋值为3,而output1反复追加2+1=3。 - 切片
append非并发安全:切片的底层数组是共享资源,多个goroutine同时执行append时,会存在数据竞争——比如多个goroutine同时修改切片的长度字段、触发扩容时的数组拷贝冲突,最终导致结果混乱。
修正方案
1. 解决循环变量捕获问题
在循环内部将i和n复制为局部变量,确保每个goroutine拿到独立的变量值:
for i, n := range input { // 复制循环变量到局部变量,避免goroutine捕获复用的变量 idx, num := i, n eg.Go(func() error { output1 = append(output1, num+1) output2[idx] = num + 1 return nil }) }
2. 解决output1的并发安全问题
针对append的并发不安全问题,有两种常见解决方式:
方式一:使用互斥锁保护切片操作
import ( "fmt" "sync" "golang.org/x/sync/errgroup" ) func main() { eg := errgroup.Group{} var mu sync.Mutex // 新增互斥锁 input := []int{0, 1, 2} output1 := []int{} output2 := make([]int, len(input)) for i, n := range input { idx, num := i, n eg.Go(func() error { mu.Lock() output1 = append(output1, num+1) mu.Unlock() output2[idx] = num + 1 return nil }) } eg.Wait() fmt.Printf("with append %+v\n", output1) fmt.Printf("with make %+v\n", output2) }
方式二:用channel收集结果(更优雅的并发结果收集方式)
import ( "fmt" "golang.org/x/sync/errgroup" ) func main() { eg := errgroup.Group{} input := []int{0, 1, 2} output2 := make([]int, len(input)) resultChan := make(chan int, len(input)) // 带缓冲的channel for i, n := range input { idx, num := i, n eg.Go(func() error { res := num + 1 resultChan <- res output2[idx] = res return nil }) } // 启动goroutine关闭channel,避免死锁 go func() { eg.Wait() close(resultChan) }() // 从channel收集结果 output1 := []int{} for res := range resultChan { output1 = append(output1, res) } fmt.Printf("with append %+v\n", output1) fmt.Printf("with make %+v\n", output2) }
运行结果
修正后两种方案都能得到预期输出:
with append [1 2 3] with make [1 2 3]
内容的提问来源于stack exchange,提问作者ussu
相关产品推荐
相关产品推荐

