Go使用goroutine并发取compute最大值context取消时返回0或死锁如何解决
问题排查与解决方案
问题根源
返回0和偶现死锁的问题主要由以下几个原因导致:
- 主协程未等待子goroutine执行:原代码中
findMax函数启动指定数量的goroutine后立刻返回,此时子goroutine还在执行compute的休眠逻辑,max变量还没被赋值,所以始终返回0。 - 并发读写无同步导致数据竞争:多个goroutine同时修改
max、num变量,没有加锁保护,会出现读写冲突,即使等待goroutine执行完结果也不准确,部分同步逻辑不当的实现还会触发死锁。 - 未实现反复调用compute的逻辑:需求要求每个goroutine在ctx取消前反复调用
compute,原代码每个goroutine只调用一次compute就退出,不符合功能要求。 - 闭包变量捕获错误:匿名goroutine直接捕获外部的
num变量,多个goroutine会同时修改同一个num,值会被随机覆盖。
修复后代码
package main import ( "context" "fmt" "math/rand" "sync" "time" ) func findMax(ctx context.Context, concurrency int) uint64 { var ( max uint64 = 0 mu sync.Mutex wg sync.WaitGroup ) // 注册要等待的goroutine数量 wg.Add(concurrency) for i := 0; i < concurrency; i++ { go func() { defer wg.Done() // 循环调用compute直到ctx被取消 for { select { case <-ctx.Done(): return default: num := compute() // 加锁保护max变量的读写,避免数据竞争 mu.Lock() if num > max { max = num } mu.Unlock() } } }() } // 等待所有goroutine全部退出后再返回结果 wg.Wait() return max } func compute() uint64 { // NOTE: This is a MOCK implementation of the blocking operation. time.Sleep(time.Duration(rand.Int63n(100)) * time.Millisecond) return rand.Uint64() } func main() { maxDuration := 2 * time.Second concurrency := 10 ctx, cancel := context.WithTimeout(context.Background(), maxDuration) defer cancel() max := findMax(ctx, concurrency) fmt.Println(max) }
改动说明
- 新增
sync.WaitGroup:等待所有启动的goroutine在ctx取消后全部退出,避免findMax提前返回。 - 新增
sync.Mutex:保护max变量的并发读写,避免数据竞争导致的结果错误。 - 每个goroutine内部增加循环逻辑:只要ctx未取消就持续调用
compute,符合反复调用的需求。 - 修复闭包变量捕获:
num变量改为goroutine内部的局部变量,避免多个goroutine同时修改同一个外部num导致值覆盖。
内容的提问来源于stack exchange,提问作者imparante
相关产品推荐
相关产品推荐

