如何从Goroutine安全更新Slice?Go并发编程问题求助
Go并发更新Slice的问题与解决方法
初始错误实现
我在尝试从并行Goroutine中更新Slice时遇到了问题,最初写出了这段明知不正确的代码:
func main() { var dwpList = []int {1, 2, 3, 4, 5, 6, 7, 8, 9, 10} var mmrpList []int var wg sync.WaitGroup ch1 := make(chan int) cores := 4 wg.Add(cores + 1) for i := 0; i < cores; i++ { go func1(&mmrpList, ch1, &wg) } iterateSamples(dwpList, ch1, &wg) close(ch1) wg.Wait() fmt.Println(mmrpList) } func iterateSamples(dwpList []int, ch1 chan<- int, wg *sync.WaitGroup) { defer wg.Done() for _, dwp := range dwpList { ch1 <- dwp } } func func1(mmrpList *[]int, ch1 <-chan int, wg *sync.WaitGroup) { defer wg.Done() for dwp := range ch1 { *mmrpList = append(*mmrpList, dwp) } }
显然这段代码无法正常工作,因为多个Goroutine同时向Slice执行append操作——Slice的底层数组是共享资源,并发append会引发数据竞争、底层数组扩容时的状态不一致等问题,最终结果完全不可预测。
当我尝试将append操作移至由额外通道、WaitGroup等控制的独立函数时,总会遇到编译错误或死锁问题(有时是WaitGroup的Wait/Done调用逻辑出错),或者程序退化为单线程执行,完全失去并发意义。
修正后的实现
调整代码结构后,我写出了如下版本,终于成功实现了需求:
func main() { var dwpList = []int {1, 2, 3, 4, 5, 6, 7, 8, 9, 10} var mmrpList []int var wg sync.WaitGroup ch1 := make(chan int) ch2 := make(chan int) cores := 4 wg.Add(1) go func() { defer wg.Done() // Get data for _, dwp := range dwpList { ch1 <- dwp } close(ch1) }() for i := 0; i < cores; i++ { wg.Add(1) go func() { defer wg.Done() // Do stuff then pass data on for dwp := range ch1 { ch2 <- dwp } }() } go func() { wg.Wait() close(ch2) }() for dwp := range ch2 { mmrpList = append(mmrpList, dwp) } fmt.Println(mmrpList) }
这个版本的核心改进点:
- 让
ch1的写入操作在单个Goroutine中执行,避免数据竞争 - 用第二个通道
ch2统一接收所有工作Goroutine的处理结果,仅在主线程中执行append操作,彻底规避了并发修改Slice的风险 - 通过独立Goroutine等待所有工作Goroutine完成后再关闭
ch2,确保主线程能完整遍历所有结果
内容的提问来源于stack exchange,提问作者shakeshuck
相关产品推荐
相关产品推荐

