You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Go的sync/errgroup包编写正确的并发for循环?

解决Go errgroup并发处理切片时的结果异常问题

问题原因分析

你的代码出现异常结果主要有两个核心问题:

  1. 循环变量捕获陷阱:for range循环中的i和n是复用的变量,所有goroutine启动后,可能循环已经执行完毕,此时它们捕获到的都是循环最后一轮的i=2和n=2,这就导致output2只有索引2被赋值为3,而output1反复追加2+1=3。
  2. 切片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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 02:01:14