Go语言WaitGroup协程引发通道重复关闭Panic问题排查与修复
解决Go并发中重复关闭通道导致的panic问题
问题根源
你的Extract函数中,for循环在进入else块处理完毕后没有终止。当pageCh被关闭后,每次执行page, more := <-pageCh都会返回(零值, false),导致else块被反复执行。第一次关闭extractedCh后,第二次执行close(extractedCh)就会触发panic: close of closed channel错误。
修正方案
不需要额外检查通道状态,直接重构循环逻辑即可解决问题,同时还能修复原代码中隐藏的循环变量捕获陷阱:
方案1:在else块添加break终止循环
func (ex Extractor) Extract(pageCh chan *types.ScrapedData, extractedCh chan *types.ScrapedData) { log.Info().Msg("Extracting data...") var wg sync.WaitGroup for { page, more := <-pageCh if more { wg.Add(1) // 将page作为参数传入goroutine,避免循环变量复用导致的错误 go func(p *types.ScrapedData) { defer wg.Done() worker, err := ex.getWorker(p) if err != nil { log.Error().Err(err).Msg("Error creating worker") } else { worker.Extract(extractedCh) } }(page) } else { log.Info().Msg("WebPage channel closed: waiting for waitgroup") wg.Wait() log.Info().Msg("WaitGroup finished closed -- closing extractedCh") close(extractedCh) break // 终止循环,避免重复进入else块 } } }
方案2:改用range遍历通道(更简洁推荐)
使用for range遍历通道时,通道关闭后循环会自动终止,代码更简洁安全:
func (ex Extractor) Extract(pageCh chan *types.ScrapedData, extractedCh chan *types.ScrapedData) { log.Info().Msg("Extracting data...") var wg sync.WaitGroup // range遍历通道,通道关闭后自动结束循环 for page := range pageCh { wg.Add(1) go func(p *types.ScrapedData) { defer wg.Done() worker, err := ex.getWorker(p) if err != nil { log.Error().Err(err).Msg("Error creating worker") } else { worker.Extract(extractedCh) } }(page) } log.Info().Msg("WebPage channel closed: waiting for waitgroup") wg.Wait() log.Info().Msg("WaitGroup finished closed -- closing extractedCh") close(extractedCh) }
额外注意点
原代码中goroutine直接捕获循环变量page存在隐患:循环变量page在每次迭代中会被复用,goroutine可能拿到的是同一个内存地址的最新值,导致处理错误。通过将page作为参数传入goroutine函数,可以避免这个问题。
内容的提问来源于stack exchange,提问作者ChrisHilborne
相关产品推荐
相关产品推荐

