channel阻塞是设计行为而非bug,触发于push/pop操作:无缓冲时双向必阻塞;有缓冲时满/空阻塞;关闭后push返回false、pop抛异常;超时未设则协程挂起等待唤醒。

Channel 阻塞不是 bug,是设计行为;不理解这点,写出来的协程代码大概率死锁。
Channel 阻塞的四种明确触发条件
阻塞只发生在 push() 和 pop() 两个操作上,具体取决于缓冲区状态和通道类型:
- 无缓冲 Channel(
new Channel(0)):push()必阻塞,直到有协程在另一端pop();反之,pop()也必阻塞,直到有协程push() - 有缓冲 Channel(如
new Channel(10)):push()在缓冲区满时阻塞;pop()在缓冲区空时阻塞 - 已关闭的 Channel:
push()立即返回false,pop()返回false或抛出异常(取决于是否设了超时) - 超时未设置且无数据可读/可写时,阻塞会一直持续——这不是卡死,是协程挂起,等待调度器唤醒
为什么用 pop() 等不到数据就卡住?
常见错误是只开一个协程 push(),另一个协程却没真正启动或提前退出,导致 pop() 永远等不到消息。典型误写:
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
$ch = new Channel(1);
Coroutine::create(function () use ($ch) {
$ch->push('done');
});
// 主协程直接 pop —— 此时 push 协程可能还没执行到 push(),就已阻塞
var_dump($ch->pop()); // 卡住
正确做法是确保生产者已就绪,或加超时:
- 用
co::sleep(0.001)让调度器切走一次(仅调试用,不推荐上线) - 更可靠的是用
$ch->pop(1.0)设 1 秒超时,返回false时做降级处理 - 或改用
WaitGroup控制“全部完成”信号,而非依赖 Channel 传值
Channel 和 WaitGroup 别混用场景
两者都用于协程同步,但语义完全不同:
-
Channel是「通信」:传递数据 + 同步,适合需要流转结果、分发任务、构建消费者模型的场景,比如连接池取连接、异步日志收集 -
WaitGroup是「计数」:只告诉主协程“所有子任务 done 了”,不传数据,适合并发请求后统一汇总、批量 IO 完成通知 - 强行用
Channel实现WaitGroup行为(比如每 done 一次 push 个 true)——浪费缓冲区、增加调度开销、还容易漏 pop 导致堆积 - 反过来,想靠
WaitGroup传递爬虫返回内容?做不到,它没数据载体
最容易被忽略的一点:Channel 的阻塞是协程级的,但它的缓冲区是固定大小的。一旦忘记 pop() 或生产者崩溃,缓冲区就会卡死,后续所有 push() 都会永久挂起——这种问题在线上不会报错,只会让部分请求无声超时。










