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

如何用Mutex正确捕获多Goroutine中的首个错误?(附Go代码)

问题分析与解决方案

现有代码的核心问题

  • 重复解锁触发panic:Goroutine里已经通过defer mu.Unlock()注册了解锁操作,后续又手动调用mu.Unlock(),这会尝试解锁一个已解锁的互斥量,直接导致运行时崩溃。
  • 锁逻辑冗余:互斥锁只需要保护共享变量sum和firstErr的读写,现有代码的解锁操作完全多余,反而引发致命问题。

修正后的正确实现

import "sync"

func Average(names []string) (int, error) {
    mu := &sync.Mutex{}
    wg := &sync.WaitGroup{}
    sum := 0
    var firstErr error

    for _, name := range names {
        wg.Add(1)
        go func(n string) {
            defer wg.Done()

            // 先执行耗时的成绩获取,不占用锁,最大化并行效率
            elem, err := GetMarks(n)

            mu.Lock()
            defer mu.Unlock() // 仅用defer保证解锁,避免重复操作

            // 只记录第一个出现的错误
            if err != nil {
                if firstErr == nil {
                    firstErr = err
                }
                return
            }

            // 无错误时累加成绩
            sum += elem
        }(name)
    }

    wg.Wait()

    // 优先返回错误
    if firstErr != nil {
        return 0, firstErr
    }

    if len(names) == 0 {
        return 0, nil
    }

    return sum / len(names), nil
}

关键改进说明

  • 移除手动解锁:只保留defer mu.Unlock(),确保不管Goroutine是正常结束还是因错误返回,互斥量都能正确解锁,彻底避免panic。
  • 合理控制锁范围:把耗时的GetMarks放在锁外执行,只有在修改共享变量sum和firstErr时才加锁,让并行逻辑真正发挥作用。
  • 错误处理严谨性:通过互斥锁保护firstErr的读写,保证只有第一个发生的错误被记录,后续错误不会覆盖它。

进阶优化(可选)

如果希望第一个错误出现后,尽快终止其他Goroutine(前提是GetMarks支持取消机制),可以用context.Context实现快速取消:

import (
    "context"
    "sync"
)

func AverageWithCancel(names []string) (int, error) {
    ctx, cancel := context.WithCancel(context.Background())
    defer cancel() // 函数退出时确保取消上下文

    mu := &sync.Mutex{}
    wg := &sync.WaitGroup{}
    sum := 0
    var firstErr error

    for _, name := range names {
        wg.Add(1)
        go func(n string) {
            defer wg.Done()

            // 传入上下文,让GetMarks支持中途取消
            elem, err := GetMarksWithContext(ctx, n)
            if err != nil {
                mu.Lock()
                defer mu.Unlock()
                if firstErr == nil {
                    firstErr = err
                    cancel() // 触发取消,通知其他Goroutine尽快停止
                }
                return
            }

            mu.Lock()
            defer mu.Unlock()
            // 已有错误时不再累加成绩
            if firstErr != nil {
                return
            }
            sum += elem
        }(name)
    }

    wg.Wait()

    if firstErr != nil {
        return 0, firstErr
    }

    if len(names) == 0 {
        return 0, nil
    }

    return sum / len(names), nil
}

这个版本能在第一个错误出现后,立刻终止其他正在执行的GetMarksWithContext,减少不必要的资源消耗。

内容的提问来源于stack exchange,提问作者ecoegor

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.02 03:07:28