waitgroup是单向递减的计数型同步,需预设总数且add()必须在协程启动前调用;barrier是可复用的栅栏型同步,动态注册协程、支持异常健壮、可跨协程共享。

WaitGroup 是计数型同步,Barrier 是栅栏型同步
WaitGroup 本质是个带阻塞语义的整数计数器:add(n) 增加待完成数量,done() 减一,wait() 阻塞直到归零。它只关心“总共几个、还剩几个”,不关心谁在哪个阶段——适合任务启动前就确定总数的场景,比如并发请求 5 个 API 后统一处理结果。
Barrier 则是“全员到齐才放行”的栅栏:Barrier::make() 创建后,每个协程调用 $barrier->wait() 会阻塞,直到所有参与协程都抵达同一道栅栏点。它不依赖预设数量,而是动态感知当前参与的协程数(首次 wait() 调用即注册),更适合需要多阶段协同的流程,比如“所有子协程先初始化,再一起发请求,再一起解析”。
关键区别在于:WaitGroup 的生命周期是单向递减的(add → done × n → wait 返回),而 Barrier 支持多次复用,每次 wait() 都是一次独立的同步点。
add() 必须在 go() 外调用,Barrier 不需要提前声明数量
WaitGroup 的 add() 必须在启动子协程前完成,否则会出现竞态:协程已执行 done(),但主协程还没 add(),导致计数器负溢出或 wait() 永久挂起。常见错误写法是把 $wg->add() 放进 go() 闭包里。
Barrier 完全没这问题:Barrier::make() 创建后,任意协程随时调用 $barrier->wait() 即可注册并等待,无需预先告知总数。它内部自动统计当前抵达的协程数,等最后一个到达时全体释放。
这意味着 Barrier 更适合动态生成协程的场景,例如从数据库查出 N 条记录,为每条启一个协程处理,你根本不知道 N 是多少,也无需先 count() 再 add(N)。
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
异常/提前退出时,WaitGroup 极易卡死,Barrier 相对健壮
WaitGroup 对异常极其敏感:done() 漏调一次,wait() 就永远阻塞;如果协程因超时、exit() 或未捕获异常提前终止,finally 或 defer 没包住 $wg->done(),主协程就彻底卡住——Swoole 不像 Go 那样有 runtime 级 panic 校验,负计数也不会自动报错,只是静默失效。
Barrier 在协程异常退出时,只要没走到 $barrier->wait(),就不参与本次同步,不影响其他协程。即使某个协程在 wait() 前崩溃,Barrier 内部会感知连接断开并清理状态(底层基于 Channel 实现),不会拖垮整个同步流程。
所以实际使用中,WaitGroup 强烈建议搭配 try/finally:
go(function () use ($wg) {
try {
// IO 操作
Co::sleep(1);
} finally {
$wg->done();
}
});
WaitGroup 不能跨协程传递,Barrier 可以共享引用
Swoole 的 Coroutine\WaitGroup 必须在协程上下文中创建和使用,且所有 add()/done() 调用必须发生在同一协程环境内。你不能在 onWorkerStart 回调里 new 一个 WaitGroup 然后传给子协程——它会直接报错或行为未定义。
Barrier 没这个限制:Barrier::make() 返回的对象是普通 PHP 对象,可以作为参数传入任意协程闭包、存入数组、甚至通过 Channel 发送,只要确保所有协程访问的是同一个实例即可。这种松耦合让它更容易嵌入复杂调度逻辑,比如在协程池、分片任务管理器中作为阶段同步工具。
真正容易被忽略的是:WaitGroup 和 Barrier 的底层都依赖 Channel,但 Barrier 的设计更贴近“通信原语”,而 WaitGroup 更像一个带语义封装的计数器——前者天然支持解耦,后者绑定强、容错弱,选错会导致调试成本陡增。










