Concurrent handler阻塞问题分析及processEvent函数排查与重构咨询
问题逐点解答
- 关于
count变量与count == 4判断
代码仅启动了2个异步任务,每个任务执行成功最多往done通道发送1次信号,count最多累加至2,永远不可能达到4。这是明显的手误逻辑错误,原本预期应该是判断count == 2来确认两个任务都执行完成,写错为4后直接导致两个任务都成功的场景下,主协程永远卡在for循环中等待信号,函数直接阻塞。 - 关于代码末尾的
close操作
这部分代码的本意是主动关闭三个通道释放内存资源,但由于前面的for循环没有正常退出路径,永远执行不到该位置,属于完全无效的冗余代码。 - 关于
wg.Wait()不可达提示
前面的for select循环只有两种走向:要么收到errc的错误信号直接return,要么永远卡在循环里等待通道信号,没有任何路径能跳出for循环执行到wg.Wait()这行,所以IDE会判定该行代码不可达。
额外存在的风险点
- goroutine泄露:
errc、done都是无缓冲通道,当主协程收到错误触发return后,另一个还未执行完成的异步任务结束后要往通道发数据时,已经没有接收方,会导致该goroutine永远阻塞在通道发送操作,出现资源泄露。 - 并发数据竞争:两个goroutine都直接修改外部声明的
err变量,没有加锁保护,会出现并发读写冲突,导致错误信息异常。 - 任务优雅退出逻辑缺失:只要有一个任务报错,主协程直接返回,没有等待另一个任务执行完毕,可能出现任务中途中断导致的资源未释放问题。
重构实现
推荐使用errgroup简化并行任务的管控逻辑,代码更简洁也更易维护:
import ( "context" "strings" "golang.org/x/sync/errgroup" ) func processEvent(i models.Foo) (string, error) { if err := func1(); err != nil { return "", err } if strings.ToUpper(i.Status) != "OK" { return "", nil } // 创建errgroup,任意任务报错会自动触发ctx取消 g, ctx := errgroup.WithContext(context.Background()) g.Go(func() error { if err := longTimeTask1(); err != nil { log.Error("%s", err.Error()) return err } return nil }) g.Go(func() error { if err := longTimeTask2(); err != nil { return err } return nil }) // 等待所有任务执行完成 if err := g.Wait(); err != nil { log.Info("event: %s", err.Error()) return "", err } return "processed", nil }
内容的提问来源于stack exchange,提问作者chancyWu
相关产品推荐
相关产品推荐

