Go语言并发场景下使用Channel向多个Slice发送数据时的数据丢失问题排查与优化方案
问题分析与解决方案
嘿,这个问题我之前写Go并发程序的时候也踩过一模一样的坑,咱们先唠唠为啥总长度会不对,再看看怎么改代码能解决问题。
问题成因
你的代码核心问题出在等待时机不对:
- 生产者goroutine发送完1000000个数字后,会执行
close(ch)然后给done通道发信号。 - 主goroutine一收到
done信号就立刻统计slice长度,但这时候三个消费者goroutine很可能还没处理完通道里剩下的元素。因为通道被关闭后,消费者的for n := range ch循环会继续读取通道内剩余的所有元素,直到通道彻底为空才会退出循环。主goroutine提前结束,导致统计时有些元素还没被append到slice里,总长度自然就小于预期了。
补充一句:你的代码里每个消费者操作的是各自独立的slice,所以不存在多个goroutine同时修改同一个slice的竞态问题,这部分是没问题的。
优化方案
最简洁靠谱的做法是用sync.WaitGroup来等待所有消费者goroutine完全处理完所有数据,再进行统计。修改后的代码如下:
import ( "fmt" "sync" ) func main() { ch := make(chan int) var wg sync.WaitGroup var bag1 []int var bag2 []int var bag3 []int // 生产者goroutine:负责生成数据并发送到通道 go func() { for i := 0; i < 1000000; i++ { ch <- i } close(ch) // 关闭通道,告知消费者不再有新数据 }() // 启动三个消费者,同时注册到WaitGroup wg.Add(3) go func() { defer wg.Done() // 确保goroutine退出时通知WaitGroup sendToBag(&bag1, ch) }() go func() { defer wg.Done() sendToBag(&bag2, ch) }() go func() { defer wg.Done() sendToBag(&bag3, ch) }() wg.Wait() // 阻塞等待所有消费者完成任务 len1 := len(bag1) len2 := len(bag2) len3 := len(bag3) fmt.Println("length of bag1:", len1) fmt.Println("length of bag2:", len2) fmt.Println("length of bag3:", len3) fmt.Println("total length:", len1+len2+len3) // 现在总数一定会是1000000 } func sendToBag(bag *[]int, ch <-chan int) { for n := range ch { *bag = append(*bag, n) } }
优化点说明
- 用
sync.WaitGroup替代原有的done通道:wg.Add(3)标记需要等待3个goroutine,每个消费者goroutine用defer wg.Done()确保无论函数如何退出,都会通知WaitGroup任务完成。 - 主goroutine调用
wg.Wait()会一直阻塞,直到所有消费者都处理完通道内的所有数据(消费者循环会读到通道为空才退出),这时候再统计长度就完全准确了。 - 生产者只需要专注于生成数据和关闭通道,逻辑更清晰,不需要额外处理done信号。
内容的提问来源于stack exchange,提问作者huibosa
相关产品推荐
相关产品推荐

