如何用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
相关产品推荐
相关产品推荐

