最直接的方式是用swoole\coroutine\channel做信号量式限流:new channel(5)即限并发5个,第6个协程在push()处阻塞等待;它只负责准入控制,业务逻辑放go()内,且需配合超时、close()、异常捕获等保障机制。

用 Channel 限制并发协程数最直接
协程本身轻量,但不加约束地 go() 会瞬间拉满资源,导致内存暴涨或 DNS/连接耗尽。最常用、最可控的方式是用 Swoole\Coroutine\Channel 做信号量式限流。
它的本质是缓冲区大小即并发上限:创建 new Channel(5) 就等于最多同时跑 5 个协程,第 6 个会阻塞在 push() 直到有协程 pop() 释放槽位。
- 缓冲区大小必须是整数,且建议 ≤
max_coroutine的 1/10,避免通道占满后新协程无法启动 - 不要在
Channel上做复杂逻辑,它只负责“准入控制”,业务处理放在go()内部 -
Channel是协程安全的,无需额外加锁,但close()后再push()会抛出InvalidArgumentException
max_coroutine 是全局硬上限,不是并发控制手段
max_coroutine 配置项(默认 3000)限制的是整个 Worker 进程能同时存在的协程总数,它防的是 OOM,不是业务级并发控制。把它设成 10000 并不意味着你能安全并发 10000 个 HTTP 请求——实际瓶颈往往在 DNS、连接池、文件描述符或远端服务响应能力。
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
- 调高
max_coroutine必须同步调大系统nofile限制,否则socket() failed: Too many open files会提前报错 - 每个协程默认栈大小 8MB,10000 协程 ≈ 80GB 内存 —— 实际应结合
stack_size调整,I/O 密集型任务可压到 256KB–1MB - 该值在
Swoole\Coroutine::set()中设置,需在Co\run()或 Server 启动前生效,运行中不可修改
别忽略 go() 外部的隐式并发陷阱
很多人以为只要没写 go() 就是串行,但 Swoole 的协程环境会让很多同步函数“自动协程化”,比如 file_get_contents()(开启 Swoole\Runtime::enableCoroutine() 后)。这会导致看似顺序写的代码,实际并发发起一堆请求。
- 检查是否误启了全局协程化:
Swoole\Runtime::enableCoroutine()会影响所有 I/O 函数,慎用于已有同步逻辑的旧项目 - DNS 查询默认是阻塞的,
gethostbyname()会卡住整个协程 —— 必须改用Swoole\Coroutine\DNS\lookup() - 数据库连接若未使用协程版驱动(如
swoole_mysql或pdo_mysql+enableCoroutine),也会退化为同步阻塞
超时与错误处理必须嵌套在每个协程内
限流只解决“数量”问题,不解决“质量”问题。一个慢协程卡住 30 秒,会持续占用通道槽位,拖垮整体吞吐。
- 每个
go()内部必须设超时:$client->set(['timeout' => 3]),而不是依赖外层sleep()或select() - HTTP 客户端要显式
close(),否则连接不释放,fd 数会缓慢上涨直至触发Too many open files - 异常不能靠外层
try/catch捕获 —— 协程内抛出的异常不会冒泡到父协程,必须在每个go()里try/catch
Channel(10) 在本地测试很稳,放到生产环境可能因某台下游服务抖动,瞬间积压 200 个等待协程 —— 此时需要的不是调大通道,而是熔断或降级。










