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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 12:22:40