Go并发编程中为何capitalized切片长度输出不一致?
问题根源:无同步并发修改切片引发的数据竞争
你的代码里,多个goroutine同时对capitalized切片执行append操作,这是典型的数据竞争场景,直接导致了每次运行结果的长度和内容都不一致。
为什么会出现这种异常?
切片的append不是原子操作,它背后包含多个步骤:
- 读取当前切片的长度和底层数组容量
- 若容量足够,直接在末尾位置写入元素并更新长度
- 若容量不足,会分配新的底层数组、复制原数据,再添加新元素,最后更新切片的指针、长度和容量
当多个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
相关产品推荐
相关产品推荐

