Go语言水分子生成同步代码死锁原因排查及修复方案
死锁诱因
- 原子处理函数无限循环阻塞等待组:
releaseOxygen、releaseHydrogen内部使用了无限for循环,goroutine启动后永远不会退出,挂载在函数上的defer wg.Done()永远不会执行,导致Make函数中的w.wg.Wait()永久阻塞,后续的结束流程完全无法触发。 - 条件变量等待逻辑倒置:
produceMolecule的等待条件写反,凑齐1个O和2个H的合法生成条件时反而进入等待,没有goroutine负责重置计数、完成分子生成,导致原子处理函数的等待条件永远无法满足,陷入互相等待的死锁。 - 原子处理逻辑错误:每个
releaseHydrogengoroutine对应1个H原子,但代码中每执行一次就往结果里写入HH,会导致结果重复;同时没有设置原子不足时的兜底退出逻辑,多余的O/H会永久等待不存在的原子,进一步加剧死锁。 - 生成计数逻辑错误:
produceMolecule未判断是否凑齐合法原子就直接累加分子数,会导致输出计数不符合预期。
修复方案
核心修改逻辑如下:
- 移除原子处理函数的无限循环,每个goroutine仅负责处理1个对应原子,处理完成后直接退出,保证
wg.Done()正常执行。 - 修正所有条件变量的判断逻辑:
produceMolecule只有在凑不够1O2H且未收到结束信号时才进入等待,凑齐原子后完成分子生成、重置计数、广播通知其他goroutine。- 原子处理函数等待时加入
finished状态判断,收到结束信号后直接退出,避免永久阻塞。
- 调整结果写入逻辑,统一在分子生成时写入结果,避免原子处理函数重复写入的问题。
- 修正分子计数逻辑,仅凑齐合法原子组合时才累加计数。
修复后的完整代码:
import ( "sync" ) // Water 结构体存储解决水分子生成问题所需的同步原语和数据 // moleculeCount 存储已生成的水分子数量 // result 存储原子拼接结果字符串 // wg 等待组用于等待所有goroutine执行完成 type Water struct { sync.Mutex wg sync.WaitGroup cond *sync.Cond moleculeCount int result string assignedO int assignedH int finished bool } // New 初始化Water结构体 func New() *Water { water := &Water{ moleculeCount: 0, result: "", assignedO: 0, assignedH: 0, finished: false, } water.cond = sync.NewCond(water) return water } // releaseOxygen 处理单个氧原子 func (w *Water) releaseOxygen() { defer w.wg.Done() w.Lock() defer w.Unlock() w.assignedO += 1 // 通知生产协程有新原子加入 w.cond.Broadcast() // 等待被消耗或者收到结束信号 for w.assignedO >= 1 && !w.finished { w.cond.Wait() } } // releaseHydrogen 处理单个氢原子 func (w *Water) releaseHydrogen() { defer w.wg.Done() w.Lock() defer w.Unlock() w.assignedH += 1 // 通知生产协程有新原子加入 w.cond.Broadcast() // 等待被消耗或者收到结束信号 for w.assignedH >= 2 && !w.finished { w.cond.Wait() } } // produceMolecule 生成水分子 func (w *Water) produceMolecule(done chan bool) { for { w.Lock() // 没凑够1个O+2个H且未结束时进入等待 for !(w.assignedO >= 1 && w.assignedH >= 2) && !w.finished { w.cond.Wait() } // 收到结束信号直接退出 if w.finished { w.Unlock() break } // 生成水分子 w.moleculeCount += 1 w.result += "HOH" // 消耗1个O和2个H w.assignedO -= 1 w.assignedH -= 2 // 通知所有等待的原子协程 w.cond.Broadcast() w.Unlock() } done <- true } func (w *Water) finish() { w.Lock() w.finished = true w.cond.Broadcast() w.Unlock() } func (w *Water) Molecules() int { return w.moleculeCount } // Make 接收输入的原子字符串,返回生成的水分子拼接结果 func (w *Water) Make(input string) string { done := make(chan bool) go w.produceMolecule(done) for _, ch := range input { w.wg.Add(1) switch ch { case 'O': go w.releaseOxygen() case 'H': go w.releaseHydrogen() } } w.wg.Wait() w.finish() <-done return w.result }
内容的提问来源于stack exchange,提问作者Marsh Mellow
相关产品推荐
相关产品推荐

