You need to enable JavaScript to run this app. Go语言使用for循环结合WaitGroup时切片元素修改结果不符合预期
优惠活动
大模型
产品
解决方案
定价
更多

Go语言使用for循环结合WaitGroup时切片元素修改结果不符合预期

问题根因

你这段代码的问题和Go版本无关,哪怕用最新版本运行也会得到全0的结果,核心原因有两点:

  • 最直接的错误:修改的是值拷贝而非原元素
    range 遍历值类型切片时,返回的第二个变量是原切片元素的完整拷贝,和原切片里的元素是完全独立的两个内存地址。你传递的&f是这个临时拷贝的地址,goroutine修改的是临时变量的属性,完全不会影响原切片foos的内容,所以打印原切片时所有Result字段还是初始的0。
  • 隐藏的并发坑:Go 1.22及更早版本的循环变量复用
    如果你使用的是Go 1.22之前的版本,for range的循环变量f是所有迭代共用同一个内存地址的。哪怕你只是想修改拷贝的内容,三次循环传入的&f都是同一个地址,三个goroutine会并发修改同一个临时变量,最终也得不到三个独立的结果。
修复方案

要让原切片的元素被正确修改,必须拿到原切片元素的真实内存地址,常见的正确写法有两种:

方案1:用索引遍历,直接取原元素地址

放弃用range出来的值拷贝,直接通过切片索引访问原元素,取地址后传入goroutine:

package main

import (
    "fmt"
    "sync"
)

type Foo struct {
    Name   string
    Result int
}

func main() {
    foos := make([]Foo, 0)
    foos = append(foos, Foo{"a", 0})
    foos = append(foos, Foo{"bb", 0})
    foos = append(foos, Foo{"ccc", 0})

    var wg sync.WaitGroup
    for i := range foos {
        wg.Add(1)
        // 直接传原切片第i个元素的真实地址
        go func(f *Foo, wg *sync.WaitGroup) {
            f.Result = len(f.Name)
            wg.Done()
        }(&foos[i], &wg)
    }
    wg.Wait()

    fmt.Println(foos) // 输出 [{a 1} {bb 2} {ccc 3}]
}

这里把&foos[i]作为goroutine参数传入,Go的函数传参是值传递,会在go语句执行时就把当前i对应的元素地址求值后拷贝给goroutine的f参数,哪怕是Go 1.22之前的版本,也不会出现循环变量复用的问题。

方案2:改用指针类型切片

如果业务允许,可以把切片改成[]*Foo类型,此时range返回的就是原元素的指针,不需要额外取地址:

package main

import (
    "fmt"
    "sync"
)

type Foo struct {
    Name   string
    Result int
}

func main() {
    // 切片元素为指针类型
    foos := make([]*Foo, 0)
    foos = append(foos, &Foo{"a", 0})
    foos = append(foos, &Foo{"bb", 0})
    foos = append(foos, &Foo{"ccc", 0})

    var wg sync.WaitGroup
    for _, f := range foos {
        wg.Add(1)
        // f本身就是原元素的指针,直接传入即可
        go func(f *Foo, wg *sync.WaitGroup) {
            f.Result = len(f.Name)
            wg.Done()
        }(f, &wg)
    }
    wg.Wait()

    // 打印时需要解引用指针
    for _, f := range foos {
        fmt.Print(*f)
    }
    // 输出 {a 1} {bb 2} {ccc 3}
}

注意:不要在闭包中直接捕获循环变量,否则Go 1.22之前会出现所有goroutine拿到循环结束后的变量值的问题。哪怕用索引遍历,下面这种写法也会出错:

// 错误示例:Go 1.22之前会输出三个相同的结果
for i := range foos {
    wg.Add(1)
    go func() {
        // 闭包捕获了循环变量i,所有goroutine拿到的都是循环结束后的i=2
        foos[i].Result = len(foos[i].Name)
        wg.Done()
    }()
}

内容的提问来源于stack exchange,提问作者赵学智

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.03 00:45:00