Go并发双协程通道整数求和 死锁与通道关闭问题求解
死锁根因
代码偶发死锁/触发panic核心是几个逻辑漏洞:
- 违反Go通道关闭基本原则:接收方(main协程)主动关闭发送端持有的通道a、b,此时两个发送协程可能正阻塞在通道写入操作上,往已关闭通道写数据会直接触发panic;就算没卡在写入,无限循环的发送协程也没有退出逻辑,会一直残留导致协程泄漏。
- 求和协程逻辑缺陷:用
select仅监听通道a,拿到a的值后就无条件阻塞读b,一旦main协程触发关闭动作,读b的操作会立刻永久阻塞;同时求和协程没有退出分支,会一直空转循环。 - 计数时序竞争:main协程的计数逻辑和通道关闭动作时序不匹配,读到第10个结果时才触发关闭,但此时求和协程可能已经开始读取第11对a、b的值,直接卡在通道读操作上,而main协程关闭c之后
range c会退出,剩下的求和协程、发送协程全部阻塞,触发死锁。 - 额外问题:
rand.Seed放在循环内高频调用,会因为时间戳精度不足生成大量重复随机值,不符合随机数生成的预期。
修复后代码
核心思路是用广播式的停止信号通知所有发送协程主动退出,严格遵循「发送方关通道」的原则,用WaitGroup等待所有协程安全退出后再做资源回收。
package main import ( "fmt" "math/rand" "sync" "time" ) // 测试参数,正式环境替换为对应值即可 const ( targetSumCount = 10 aSendInterval = 100 * time.Millisecond bSendInterval = 300 * time.Millisecond chanBufferSize = 10 ) func main() { // 随机种子仅需初始化一次 rand.Seed(time.Now().UnixNano()) a := make(chan int, chanBufferSize) b := make(chan int, chanBufferSize) c := make(chan string, chanBufferSize) // 全局停止信号,用于广播通知所有协程退出 stop := make(chan struct{}) var wg sync.WaitGroup // 启动生成协程A wg.Add(1) go func() { defer wg.Done() for { select { case <-stop: // 收到停止信号立刻退出,不再写入通道 return case a <- rand.Intn(101): time.Sleep(aSendInterval) } } }() // 启动生成协程B wg.Add(1) go func() { defer wg.Done() for { select { case <-stop: return case b <- rand.Intn(101): time.Sleep(bSendInterval) } } }() // 启动求和协程 go func() { for i := 0; i < targetSumCount; i++ { // 严格按第n对配对,读满目标次数就停止 ai := <-a bi := <-b c <- fmt.Sprintf("%d + %d = %d", ai, bi, ai+bi) } // 凑够结果后广播停止信号,关闭结果通道 close(stop) close(c) }() // 读取所有计算结果 for res := range c { println(res) } // 等待所有发送协程完全退出,确保没有协程再写入a、b wg.Wait() // 此时关闭a、b绝对安全,无写入风险 close(a) close(b) }
逻辑说明
- 停止信号通道
stop用无锁的广播机制实现协程退出通知,不需要给每个协程单独传退出信号,关闭stop通道时所有监听它的协程都会立刻收到退出事件,非常适合多协程的停止场景。 - 两个发送协程把「停止信号监听」和「通道写入」放在同一个
select块中,不管是收到停止信号还是写入完成,都不会出现永久阻塞的情况,从根源上避免了协程泄漏。 - 求和协程直接按目标数量循环读取配对值,凑够数量后主动发停止信号、关闭结果通道,完全避免了多协程之间的计数时序竞争:结果通道关闭后,main协程的
range会读完通道内所有剩余值再自然退出,不会出现空读阻塞。 sync.WaitGroup用来等待两个发送协程完全执行完退出逻辑,确认没有任何协程会再往a、b通道写入数据后,再关闭两个输入通道,严格遵循Go通道的使用规范,不会出现写关闭通道的panic。- 随机种子移到main函数入口只初始化一次,保证随机数生成的均匀性。
内容的提问来源于stack exchange,提问作者Frederik B.
相关产品推荐
相关产品推荐

