协程内变量不能直接跨协程共享,因每个协程拥有独立栈帧和局部变量空间;闭包捕获仅为值拷贝或受限引用,无法实现结果回传;应使用channel安全传递数据,waitgroup仅用于同步完成状态,context适用于trace_id等元数据透传。

协程内变量不能直接跨协程共享
协程是独立调度单元,每个协程拥有自己的栈帧和局部变量空间。你在主线程里定义的 $name = '',再用 co(function() use ($name) { ... }) 闭包捕获,实际传进去的是值拷贝,不是引用;即使改成 use (&$name),也仅在闭包内部生效,协程结束后主线程仍看不到修改——因为协程退出后,闭包作用域就销毁了。
常见错误现象:主线程 var_dump($name) 始终为空、null 或初始值;调试时发现数据“消失”了,其实是根本没传回来。
- 不要依赖
use传递可变状态来“同步”结果 - 避免在协程外声明全局变量或静态属性来“中转”,这会破坏协程隔离性,高并发下极易脏读
- PHP 的引用传递(
&$var)在协程间不生效,协程切换时引用链断裂
用 Channel 实现安全的数据回传
Swoole\Coroutine\Channel 是最常用、最可控的协程间通信方式,适合“生产者→消费者”模型,比如批量查询后汇总结果。
关键点在于容量设置和消费时机:
- 初始化时指定容量,如
new Swoole\Coroutine\Channel(10),若生产数量超过容量且无消费者及时 pop,后续push()会挂起生产协程 - 消费必须在所有生产协程启动后、且确保它们已结束(或至少开始 push)再执行
pop(),否则pop()会永久阻塞 - 若不确定生产数量,可用
$channel->close()配合while ($channel->length() > 0) { $channel->pop(); }安全遍历
示例片段:
go(function () use ($channel) {
$users = Db::select('SELECT count(*) as ct FROM t_orders');
foreach ($users as $user) {
$channel->push($user->ct);
}
});
// 等待全部协程完成后再消费
for ($i = 0; $i pop();
}
WaitGroup 更适合“等待完成”而非“取值”
Swoole\Coroutine\WaitGroup 的核心职责是同步协程生命周期,不是数据容器。它能告诉你“10 个协程都跑完了”,但不负责帮你收集结果。
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
如果你硬要在 WaitGroup 场景里传值,必须配合外部可写变量(如数组引用),但要注意:
- 必须用
&$data显式传引用,否则仍是副本 - 多个协程同时写入同一数组,PHP 数组本身线程/协程安全,但需确保键不冲突(例如用
$data[] = $val是安全的) - 不能在
wait()前读取$data,否则可能只拿到部分结果
它比 Channel 轻量,但缺乏数据流控制能力——适合日志记录、状态标记等无需返回值的场景。
Context 透传适用于 Trace ID 等元数据
真正需要“上下文贯穿”的场景(如链路追踪),要用 Swoole\Coroutine::getContext() 和 setContext()。它把数据绑定到当前协程,协程切换时自动延续,且不会被其他协程污染。
典型用法:
- 入口处生成
$traceId = uniqid('t_', true),存入上下文:Co::setContext(['trace_id' => $traceId]) - 子协程中通过
Co::getContext()['trace_id']取出,用于日志打点或 HTTP Header 透传 - 注意:context 是协程私有,不可跨协程直接读写;也不能存大对象,影响调度性能
别把它当成通用数据管道——它设计目标是轻量元信息,不是业务结果载体。
复杂点在于:Context 不解决“怎么把查询结果带出来”,它只解决“怎么让日志知道这是同一个请求”。数据回传和上下文透传是两件事,混用会导致逻辑混乱。










