Go并发测试核心是暴露竞态并验证同步机制,需用-race检测数据竞争、channel/WaitGroup控制执行节奏、模拟高并发与边界条件,并用子测试隔离不同同步策略。

在 Go 中测试协程并发逻辑,核心不是“多开几个 goroutine 看它跑不跑”,而是有意识地暴露竞态、验证同步机制是否真正生效。关键在于:用 go test -race 捕获数据竞争,用 sync/atomic 或 channel 显式建模并发行为,并通过可控的调度干扰(如 runtime.Gosched、time.Sleep)或工具(如 golang.org/x/sync/errgroup)增强测试覆盖。
用 -race 标志发现隐藏的数据竞争
Go 自带的竞态检测器是并发测试的第一道防线。它不能保证逻辑正确,但能帮你揪出绝大多数非同步共享变量访问。
- 始终以 go test -race 运行并发相关测试,哪怕本地没复现也要加
- 避免在测试中直接读写未加锁的全局变量或结构体字段;若必须共享,优先用 sync.Mutex 或 sync.RWMutex
- 注意:-race 对 channel 和 atomic 操作友好,但对粗粒度的 sleep/wait 不敏感——它检测的是内存访问冲突,不是逻辑死锁
用 channel 或 sync.WaitGroup 控制执行节奏
单纯启动 goroutine 并立刻断言结果,往往因调度不确定性而误报或漏报。需显式等待所有协程完成,并确保观察时机合理。
- 用 sync.WaitGroup 等待所有 goroutine 结束后再检查最终状态
- 用 channel 传递中间结果或信号,比如每个 goroutine 完成后 send true 到 done chan,主 goroutine recv N 次
- 避免用 time.Sleep 等待——它不可靠且拖慢测试;改用 select + timeout 配合 channel 实现可中断等待
模拟高并发压力与边界条件
真实场景下,并发问题常在高负载或临界点爆发。测试要主动逼近这些边界。
立即学习“go语言免费学习笔记(深入)”;
- 把并发数从 2 扩到 100+,观察计数器、map 写入、资源池分配等是否仍一致
- 对共享 map 使用 sync.Map 或加锁,并在测试中混合读写操作(如 80% 读 + 20% 写)
- 故意让 goroutine 在临界区前/后调用 runtime.Gosched(),增加调度切换概率,提高竞态触发率
用子测试(subtest)隔离并发场景
不同并发策略(如 mutex vs RWMutex vs channel)应分开验证,避免状态污染。
- 用 t.Run() 创建命名子测试,每个子测试独占一组共享资源(如新初始化的 struct)
- 在子测试内完整构建 goroutine、等待、断言流程,确保可重复、可独立运行
- 例如:t.Run("with mutex", func(t *testing.T) { ... }) 和 t.Run("with channel", func(t *testing.T) { ... })










