You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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.

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 02:36:15