应使用 sync.WaitGroup 显式等待 goroutine 完成:启动前 wg.Add(1),结束时 wg.Done(),主协程调用 wg.Wait();channel 由发送方在所有发送完成后关闭;避免 time.Sleep 和未同步的共享变量,启用 -race 检测竞态。

goroutine 启动后立即退出,测试没跑完就结束了怎么办
Go 的 testing 包默认不等待 goroutine 结束,go func() { ... }() 启动后主测试函数一返回,整个测试就结束,导致并发逻辑根本没执行完。这不是 bug,是设计使然——goroutine 是异步的,测试框架不会自动同步。
- 用
sync.WaitGroup显式等待所有 goroutine 完成:在启动前wg.Add(1),每个 goroutine 结束时调用wg.Done(),主 goroutine 调用wg.Wait() - 避免在测试中用
time.Sleep等待,它不可靠、慢、且掩盖真实同步问题 - 注意
WaitGroup必须在所有 goroutine 启动前完成Add,否则可能 panic:比如循环中先go func(i int)再wg.Add(1),i 可能被闭包捕获为错误值
channel 关闭时机不对,导致 range 死锁或 panic
测试中常通过 channel 收集 goroutine 输出结果,但若关闭过早(如在发送 goroutine 还没发完时就 close(ch)),range ch 会提前退出,漏数据;若关闭过晚或忘记关,range 永远阻塞,测试超时失败。
- 只由“发送方”负责关闭 channel,且必须在所有发送操作完成后关闭(典型做法:用
WaitGroup确保全部发送完毕后再close(ch)) - 接收方永远不要 close channel,也不应假设 channel 已关闭而直接
close(ch) - 如果多个 goroutine 同时向同一 channel 发送,不能由任意一个发送者关——需额外协调,例如用另一个 channel 或
sync.Once
测试中使用 select + time.After 模拟超时,但实际行为不符合预期
写并发测试时,常想“等 100ms,如果没收到结果就报错”。但 select 中的 time.After 每次都会新建一个 timer,若放在循环里反复调用,会产生大量泄漏 timer,且逻辑易错。
- 把
timeout := time.After(100 * time.Millisecond)提到循环外,复用同一个 channel - 确保
select分支中对 channel 的读写不会阻塞其他分支(例如向未缓冲的 channel 发送而无人接收,会导致该 case 永远不满足) - 更健壮的做法是用
context.WithTimeout,配合ctx.Done(),尤其适合嵌套或可取消的测试场景
并发测试出现随机失败(flaky test),怎么定位和稳定复现
这类问题往往源于竞态(race):多个 goroutine 无保护地读写同一变量,或依赖未同步的执行顺序。Go 自带 race detector,但必须显式启用,否则编译器不会报错。
立即学习“go语言免费学习笔记(深入)”;
- 运行测试时加
-race标志:go test -race -v ./...
- 所有共享变量(包括 struct 字段、全局变量、闭包捕获的变量)都需加锁(
sync.Mutex)或用原子操作(atomic.AddInt64等) - 避免用
fmt.Println或log.Print做“调试同步”,它们不是同步原语,也不能替代锁 - 测试中尽量减少对外部状态(如文件、网络、时间)的依赖,改用内存模拟或接口 mock
并发测试最难的不是写 goroutine,而是让“不确定性”变得确定:每处共享、每次发送、每个等待点,都要有明确归属和生命周期。漏掉一个 wg.Done(),少关一个 channel,或者多启一个没受控的 goroutine,测试就会在 CI 里间歇性崩掉。










