Barrier是协程同步中专用于“全部到达后同时解除阻塞”的原语,不关心顺序、返回值或个体状态,与WaitGroup(关注完成计数)、Channel(关注数据传递)解决不同维度问题。
Barrier 不是协程同步的“替代品”,而是协程同步中一种特定语义的实现方式:它只关心“全部到达”,不关心顺序、返回值或谁先谁后。
很多人误以为 Barrier 是 WaitGroup 的简化版,其实它们解决的问题维度不同:
WaitGroup 关注“计数”和“完成通知”,适合需要知道每个子任务是否成功、或需按顺序收集结果的场景;Channel 关注“数据传递”和“流控”,适合有明确生产者-消费者关系、需跨协程传值的场景;Barrier 只做一件事:所有协程调用一次 wait() 后,全部同时解除阻塞——它不记录谁完成了、没完成、返回了什么,甚至不暴露“当前还剩几个没到”。因为 Barrier::wait() 底层依赖 PHP 引用计数 + 协程挂起机制。一旦在非协程环境(比如主进程线程)中调用,会直接阻塞整个进程,而不是挂起当前协程。
onWorkerStart 回调里直接 Barrier::wait($barrier) → 进程卡死;wait() 总是在 Coroutine::create() 或 run() 内部调用;$worker->eventLoop 必须设为 Swoole::class 或 Swow::class,否则 Barrier 无法识别驱动类型而报错。Barrier 的生命周期靠 PHP 引用计数自动管理,但这个机制容易被忽略导致死锁或提前释放:
use($barrier) 显式捕获屏障对象,否则引用计数不增加,wait() 会立刻返回;$barrier 引用,否则在所有子协程退出前,屏障对象就可能被析构;unset($barrier) —— 这会提前减引用,造成部分协程永远等不到释放。虽然文档说 Barrier 支持 Swoole/Swow/Fiber 多驱动,但在 Swoole v6.2 实际使用中仍有细节差异:
Runtime::enableCoroutine(SWOOLE_HOOK_ALL),Barrier::wait() 行为稳定;SWOOLE_HOOK_STREAM),Barrier 可能无法正确感知协程退出,导致超时或假死;HyperfCoroutineBarrier 是封装层,底层仍走 Swoole 的 SwooleCoroutineBarrier,但默认 timeout 是 -1(无限等待),线上务必显式传入 $timeout 参数防雪崩。真正容易出问题的地方不在写法,而在“谁该 hold 住 barrier 引用”——主协程、子协程、回调闭包,三者缺一不可,漏一个就可能让 wait 永远不返回。