hyperf 的 channel 是 swoole 协程通道,仅限单协程内通信,非队列或消息中间件;必须在协程中使用,支持阻塞式 push/pop,需设容量防内存溢出,不可跨请求/进程,与 context 分工明确。

Hyperf 的 Channel 不是队列,也不是消息中间件,它只在单个协程生命周期内有效,跨协程通信必须靠它,但跨进程、跨请求、跨 Worker 完全无效。
Channel 初始化和基本 push/pop 用法
Hyperf 中的 Channel 来自 Swoole 原生协程通道(Swoole\Coroutine\Channel),不是 Hyperf 自研组件。它不依赖 Redis 或其他外部服务,纯内存操作,但必须在协程上下文中使用——直接在非协程环境(如 __construct、同步控制器方法)中 new 一个 Channel,会报错或行为不可预测。
常见错误现象:Fatal error: Uncaught Swoole\Exception: must be called in the coroutine
- 必须用
co(function() { ... })或go()启动协程后再创建 -
push()和pop()都是阻塞式调用:空时pop()yield,满时push()yield(默认容量为 -1,即无限,但生产环境建议显式设限) - 不要在
pop()后再做耗时 I/O(比如$client->get()),否则会卡住整个协程调度;应先 pop,再并发发起 I/O
示例:
co(function() {
$channel = new \Swoole\Coroutine\Channel(2); // 容量为 2
co(function() use ($channel) {
$channel->push('task-1');
$channel->push('task-2'); // 此时 channel 已满
$channel->push('task-3'); // 协程在此 yield,等待有人 pop
});
\co::sleep(0.1);
echo $channel->pop(); // 'task-1'
echo $channel->pop(); // 'task-2'
});
Channel 用于多协程协作时的数据同步
典型场景是「多个生产者 + 一个消费者」或「一个生产者 + 多个消费者」,比如并发请求后统一收口处理结果。注意:多个协程同时 pop() 时,谁先抢到谁拿走数据,无顺序保证(除非加锁或用 Channel 配合 Context 控制)。
容易踩的坑:
- 误以为
Channel能跨 HTTP 请求共享数据——不行。每个请求是独立协程,Channel实例不复用,也不传递 - 在 WebSocket
onMessage里把$channel存成类属性,然后在另一个协程里读——协程上下文已销毁,$channel可能已被 GC 或指向错误实例 - 未设置超时,
pop()无限等待导致协程永久挂起(可配合Channel::pop(float $timeout = -1)使用)
正确做法是:每次协作逻辑内新建 Channel,或通过闭包 use 显式传递,不依赖任何类成员变量。
Channel 和 Context::set/get 的分工边界
Channel 解决的是「协程之间传值」,Context::set() 解决的是「同一个协程内跨函数传值」。两者不能混用。
例如:你在一个协程里发起两个 HTTP 请求,想等它们都返回后再合并结果——用 Channel;但你想在中间件里存下 user_id,后续控制器方法里读取——用 Context::set('user_id', $uid)。
-
Channel是“横向”通信(协程 A → 协程 B) -
Context是“纵向”通信(中间件 → 控制器 → Service) - 混合使用时,切忌把
Channel实例塞进Context:它无法序列化,且生命周期难管理
如果你看到代码里写 Context::set('channel', $channel),基本可以判定设计出错了。
Channel 容量设置和内存泄漏风险
Channel 默认容量是 -1(无限),但实际项目中不建议这么用。因为一旦生产者 push 速度远大于消费者 pop 速度,内存会持续增长,最终 OOM。
性能影响明显:
- 容量设为
N时,第N+1次push会 yield,触发协程调度切换,带来轻微开销,但换来可控内存 - 完全不设限,在高并发压测下可能瞬间吃光 Worker 进程内存
- 别用
Channel替代 Redis 队列做任务分发——它没有持久化、没有重试、不支持跨进程
建议:根据业务峰值预估,设一个合理上限(如 100~1000),并在监控中观察 Channel::length() 和 Channel::isEmpty() 状态,必要时告警。
真正容易被忽略的一点:Channel 的生命周期完全由 PHP 引用计数控制。只要还有变量引用它,GC 就不会回收。协程退出后若仍有闭包或静态变量持有了 $channel,就会造成内存滞留——这比忘记 close 文件句柄更隐蔽。











