Hyperf中WaitGroup的核心作用是同步等待一组协程全部完成。它通过add()预设任务数、done()标记单个完成、wait()阻塞等待计数归零来实现,要求add必须在协程启动前调用且数量严格匹配,done必须在每个协程内用defer或finally确保执行一次。

Hyperf 中 WaitGroup 的核心作用是什么?
它不是用来替代协程调度的,而是解决「等待一组协程全部完成」这个具体问题。比如你并发发起 5 个 HTTP 请求,需要等它们全返回再统一处理结果——这时 WaitGroup 就比手写 Channel 或轮询 isFinished 更轻量、更语义明确。
怎么正确初始化和调用 add()、done()?
必须在协程启动前调用 add(),且数量要和实际启动的协程数严格一致;done() 必须在每个协程内部调用,且只能调用一次。漏调、多调、在协程外调用都会导致 wait() 永久阻塞或 panic。
常见错误写法:
// ❌ 错误:add() 在协程里调用,可能导致还没 add 就 wait 了
$wg = new WaitGroup();
go(function () use ($wg) {
$wg->add(1); // 这里 add 太晚!
// ... do work
$wg->done();
});
$wg->wait(); // 立刻返回,没等任何事
正确做法:
$wg = new WaitGroup();
$urls = ['https://a.com', 'https://b.com', 'https://c.com'];
$wg->add(count($urls)); // ✅ 提前加好总数
foreach ($urls as $url) {
go(function () use ($url, $wg) {
try {
$resp = Http::get($url);
// ... 处理响应
} finally {
$wg->done(); // ✅ 每个协程结束时调用
}
});
}
$wg->wait(); // ✅ 安全等待全部完成
WaitGroup 和 Channel 或 Co::wait() 有什么区别?
WaitGroup 不传数据,只做同步;Channel 适合收集返回值或流式通信;Co::wait() 是底层 API,已废弃,不要用。
- 要等 N 个协程结束但不关心返回值 → 用
WaitGroup - 要汇总每个协程的结果(如 5 个请求的 body)→ 配合
Channel或Array+Lock - 需要超时控制 →
WaitGroup本身不支持,得套一层Co::sleep()或用Channel::pop($timeout)模拟
注意:WaitGroup 的 wait() 是协程安全的,但不是线程安全的——别在多进程模式下跨进程共享同一个实例。
为什么 wait() 有时不返回,或者报 InvalidArgumentException?
绝大多数情况是计数错位:要么 add() 没调,要么某个协程 panic 了没走到 done(),或者重复调用了 done()。
调试建议:
- 用
try/finally包裹协程主体,确保done()总被执行 - 避免在异常分支里提前 return,跳过
done() - 不要复用
WaitGroup实例:一次wait()后,该实例不可再用于新任务 - Hyperf 2.2+ 的
WaitGroup支持getCount(),可在wait()前打印看看是否为 0
最隐蔽的坑:协程里用了 go() 启动子协程,但把 done() 写在父协程里——子协程跑多久,父协程就卡多久,done() 实际没被调。


















