Go并发执行多个返回bool的校验函数取首个false出现死锁如何解决?
问题根因分析
- 死锁第一个触发点:启动校验任务的匿名函数是同步执行的,没有为它开启单独goroutine。无缓冲通道
q在写入时必须有对应的goroutine同时读取,此时receive函数还未开始接收q的信号,主goroutine直接卡在q <- true位置,后续所有通道读写都无法执行。 - 死锁第二个触发点:无缓冲通道
c每次写入必须有对应的接收方,当receive函数提前返回(比如拿到第一个false)或者只接收1个结果就退出,剩下的校验goroutine尝试往c写入结果时永远找不到接收方,会永久阻塞,导致wg.Wait()永远无法完成。
修复方案
核心调整逻辑如下:
- 将启动校验任务的逻辑放到独立goroutine执行,避免阻塞主流程
- 给
c通道设置等于校验函数数量的缓冲,就算没有接收方,写入也不会阻塞 - 移除冗余的
q通道,所有校验任务执行完成后直接关闭c通道,通知接收逻辑所有任务已跑完 - 新增context取消信号,拿到第一个false结果后通知剩余未执行完的校验任务提前退出,节省资源
修复后可正常运行的代码:
package main import ( "context" "fmt" "sync" "time" ) var wg sync.WaitGroup func main() { v := []func(ctx context.Context, c chan<- bool){ a1, a2, a3, } // 通道缓冲大小等于校验函数数量,写入不阻塞 c := make(chan bool, len(v)) ctx, cancel := context.WithCancel(context.Background()) defer cancel() // 启动校验任务的逻辑放到独立goroutine go func() { for _, f := range v { wg.Add(1) go f(ctx, c) } // 所有校验任务跑完后关闭通道 wg.Wait() close(c) }() p := receive(ctx, cancel, c) fmt.Println("校验结果:", p) } func receive(ctx context.Context, cancel context.CancelFunc, c chan bool) bool { for v := range c { if !v { // 拿到第一个false,提前取消所有剩余校验任务 cancel() fmt.Println("Received ", v) return false } } // 通道关闭说明所有校验都通过 fmt.Println("所有校验通过") return true } func a1(ctx context.Context, c chan<- bool) { defer wg.Done() select { case <-ctx.Done(): // 已经有失败的校验了,直接退出 fmt.Println("a1 提前退出") return case <-time.After(time.Millisecond * 2000): c <- true fmt.Println("Finish 1") } } func a2(ctx context.Context, c chan<- bool) { defer wg.Done() select { case <-ctx.Done(): fmt.Println("a2 提前退出") return case <-time.After(time.Millisecond * 2000): c <- true // 测试提前退出场景可改为false fmt.Println("Finish 2") } } func a3(ctx context.Context, c chan<- bool) { defer wg.Done() select { case <-ctx.Done(): fmt.Println("a3 提前退出") return case <-time.After(time.Millisecond * 2000): c <- true fmt.Println("Finish 3") } }
内容的提问来源于stack exchange,提问作者Raphael Salomao
相关产品推荐
相关产品推荐

