waitgroup 的 wait() 必须在协程中调用,正确入口为 coroutine::run() 闭包或 swoole server 自动协程回调;add() 需在 go() 前执行,done() 必须用 try-finally 保证全覆盖;wait() 必须设超时并判断返回值。

WaitGroup 必须在协程环境里调用 wait()
直接在 CLI 脚本或 WorkerStart 回调里 new 一个 WaitGroup 然后调用 wait(),会立刻报错:Uncaught SwooleError: wait() must be called in coroutine 或者静默卡死。这是因为 wait() 本质是挂起当前协程,依赖 Swoole 协程调度器唤醒——没有协程环境,就永远等不到唤醒。
正确入口只有两类:
-
Coroutine\run()包裹的闭包内 - Swoole HTTP/Server 的
onRequest、onReceive等已自动开启协程的回调中
别在 onWorkerStart 或普通 PHP 文件顶层写 $wg->wait(),那不是协程上下文。
add() 必须在 go() 之前,且不能在子协程里调用
常见错误是把 $wg->add() 放进 go() 闭包里,比如:
go(function () use ($wg) {
$wg->add(); // ❌ 错!竞态:主协程可能已调用 wait(),但子协程还没执行到这行
// ...
$wg->done();
});
正确做法是:先 add(),再 go():
-
$wg->add(1)或$wg->add()(默认 +1)必须在go()之前执行 - 如果启动 N 个协程,就调用
$wg->add(N)一次,或 N 次$wg->add() - 计数器初始值必须 ≥0,传负数会抛
InvalidArgumentException
done() 必须成对、全覆盖,推荐用 try-finally
漏掉一次 done(),wait() 就永远卡住;多调一次,触发 SwooleError: counter can not be negative。异常路径最容易漏掉 done()。
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
安全写法只有一种:
go(function () use ($wg) {
try {
// 可能抛异常的业务逻辑,比如 co::sleep()、HTTP 请求、DB 查询
co::sleep(1.5);
} finally {
$wg->done(); // ✅ 保证无论成功失败都执行
}
});
注意:defer 在协程退出时才触发,但 Swoole 的 WaitGroup 不支持 defer 绑定(它不是语言级 defer),必须显式写 try-finally。
wait() 必须设超时,返回值要判断
$wg->wait() 不传参等于无限等待。生产环境绝不允许这样写——一个子协程崩溃或忘记 done(),整个请求就 hang 住。
正确用法:
- 传浮点秒数,例如
$wg->wait(5.0)表示最多等 5 秒 - 超时后返回
false,需主动判断:if (!$wg->wait(3.0)) { /* 超时处理 */ } - 超时计时从
wait()调用开始,不是从第一个add()开始 - 超时后计数器仍保持原值,
wait()不会重置或清零
真正难的不是语法,是确保每个 go() 都对应一次 add(),每条执行路径(包括所有 catch 和 return)都走到 done()。漏一个,问题就藏得极深,日志里看不出痕迹,只能靠超时暴露。










