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

Go并发编程中为何capitalized切片长度输出不一致?

问题根源:无同步并发修改切片引发的数据竞争

你的代码里,多个goroutine同时对capitalized切片执行append操作,这是典型的数据竞争场景,直接导致了每次运行结果的长度和内容都不一致。

为什么会出现这种异常?

切片的append不是原子操作,它背后包含多个步骤:

  1. 读取当前切片的长度和底层数组容量
  2. 若容量足够,直接在末尾位置写入元素并更新长度
  3. 若容量不足,会分配新的底层数组、复制原数据,再添加新元素,最后更新切片的指针、长度和容量

当多个goroutine同时执行append时,它们会同时读取切片的状态(长度、容量),然后各自执行写入/扩容操作。比如:

  • 两个goroutine同时读到切片长度为0,都尝试在索引0的位置写入元素,最终只会保留其中一个的结果,另一个的元素被覆盖
  • 若刚好触发扩容,多个goroutine可能各自生成新的底层数组,最后只有最后完成赋值的那个数组会被保留,前面的操作结果全部丢失

解决方法

方案1:用互斥锁保护共享切片

通过sync.Mutex确保同一时间只有一个goroutine能修改切片:

import (
    "fmt"
    "sync"
    "unicode"
    "time"
)

func main() {
    data := []rune{'a', 'b', 'c', 'd'}
    var capitalized []rune
    var mu sync.Mutex

    capIt := func(r rune) {
        upper := unicode.ToUpper(r)
        mu.Lock()
        capitalized = append(capitalized, upper)
        mu.Unlock()
        fmt.Printf("%c done!\n", r)
    }

    fmt.Printf("Before: %c\n", capitalized)
    for i := 0; i < len(data); i++ {
        go capIt(data[i])
    }
    time.Sleep(100 * time.Millisecond)
    fmt.Printf("After: %c\n", capitalized)
}

方案2:用Channel收集结果(更符合Go并发设计思想)

避免共享内存,通过Channel传递每个goroutine的处理结果,最后统一收集:

import (
    "fmt"
    "unicode"
)

func main() {
    data := []rune{'a', 'b', 'c', 'd'}
    resultChan := make(chan rune, len(data))

    capIt := func(r rune) {
        upper := unicode.ToUpper(r)
        resultChan <- upper
        fmt.Printf("%c done!\n", r)
    }

    fmt.Printf("Before: %c\n", []rune{})
    for i := 0; i < len(data); i++ {
        go capIt(data[i])
    }
    
    // 收集所有结果
    var capitalized []rune
    for i := 0; i < len(data); i++ {
        capitalized = append(capitalized, <-resultChan)
    }
    close(resultChan)
    
    fmt.Printf("After: %c\n", capitalized)
}

方案3:用WaitGroup替代Sleep(更可靠的等待方式)

原代码用time.Sleep等待goroutine完成是不可靠的——你无法保证所有goroutine都能在指定时间内执行完毕。用sync.WaitGroup可以精准等待所有goroutine结束:

import (
    "fmt"
    "sync"
    "unicode"
)

func main() {
    data := []rune{'a', 'b', 'c', 'd'}
    var capitalized []rune
    var mu sync.Mutex
    var wg sync.WaitGroup

    capIt := func(r rune) {
        defer wg.Done()
        upper := unicode.ToUpper(r)
        mu.Lock()
        capitalized = append(capitalized, upper)
        mu.Unlock()
        fmt.Printf("%c done!\n", r)
    }

    fmt.Printf("Before: %c\n", capitalized)
    wg.Add(len(data))
    for i := 0; i < len(data); i++ {
        go capIt(data[i])
    }
    wg.Wait() // 等待所有goroutine执行完成
    fmt.Printf("After: %c\n", capitalized)
}

额外排查技巧

可以用Go官方提供的go run -race命令运行代码,它会自动检测出数据竞争问题,是排查并发bug的常用工具。

内容的提问来源于stack exchange,提问作者Ali Hosseini Nezhad

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 17:43:12