重点解析 Swoole 协程中的并发请求如何实现?

作者:袖梨 2026-07-13
协程并发请求无需手动 co::sleep,核心是调度器自动挂起/唤醒协程;只要使用协程友好的客户端(如 SwooleCoroutineHttpClient),直接 go 启动即可并行执行,$cli 实例须单协程内新建且不可复用。

协程并发请求必须用 go + co::sleep 或 I/O 等待?

不是。Swoole 协程并发的核心是「不阻塞」,不是靠 co::sleep 模拟。真正并发请求靠的是协程调度器自动挂起/唤醒,只要底层函数是协程友好的(比如 SwooleCoroutineHttpClientco::curl_execRedis::connect),发起多个请求时无需手动 sleep,直接 go 启动即可并行执行。

常见错误是写成这样:

go(function () {    $cli = new SwooleCoroutineHttpClient('httpbin.org', 443, true);    $cli->get('/delay/1');    echo "done 1n";});go(function () {    $cli = new SwooleCoroutineHttpClient('httpbin.org', 443, true);    $cli->get('/delay/2');    echo "done 2n";});

这段代码实际耗时约 2 秒(非串行),因为两个协程在等待网络响应时被调度器自动让出 CPU,彼此不抢占——这才是并发本质。不需要加 co::sleep,加了反而拖慢。

SwooleCoroutineHttpClientget/post 是同步阻塞还是协程挂起?

是协程挂起。调用 $cli->get() 时,如果底层 socket 尚未就绪,当前协程会立即让出控制权,调度器转去运行其他就绪协程;等 socket 可读时再唤醒该协程继续执行。整个过程对用户代码透明。

但要注意几个关键点:

  • $cli 实例不能跨协程复用(每个 go 块里必须新建)
  • 必须在协程环境里创建和调用(比如 go 内、onRequest 回调里),否则报错 ERRORswClient_send: send() failed, Error: Protocol not supported[93]
  • 超时设置要显式指定:$cli->set(['timeout' => 5]),否则默认 0.5 秒,容易误判失败
  • HTTPS 请求必须传 true 第三个参数,且确保 OpenSSL 已启用,否则连接直接失败,错误信息是 SSL_connect failed

为什么并发 100 个请求,实际只发出去 20 个?

大概率是系统级限制或 Swoole 配置没调优。Swoole 默认不限制协程数,但操作系统对单进程可打开文件描述符(fd)数量有限制(通常是 1024),而每个 SwooleCoroutineHttpClient 至少占用 1 个 fd(TCP 连接 + SSL 句柄可能更多)。

检查和调整方式:

  • 运行 ulimit -n 查看当前限制,临时提高: ulimit -n 65535
  • 确认 Swoole 版本 ≥ 4.8.0,低版本存在 fd 复用 bug,高并发下泄漏
  • 避免在循环里无节制 go,建议配合 chan 控制并发度,例如最多同时跑 30 个:
$chan = new SwooleCoroutineChannel(30);for ($i = 0; $i < 100; $i++) {    $chan->push(true); // 占位    go(function () use ($i, $chan) {        defer { $chan->pop(); }; // 完成后释放        $cli = new SwooleCoroutineHttpClient('httpbin.org', 443, true);        $cli->get("/delay/" . rand(1, 3));    });}

协程 HTTP 客户端能复用连接吗?

可以,但仅限于同一个 $cli 实例的连续请求(keep-alive),且服务端必须支持。默认开启 keep_alive,但注意:

  • 复用只发生在同一协程内多次调用 get/post;跨协程的 $cli 实例互不影响
  • 若中间有重定向($cli->set(['follow_location' => true])),keep-alive 可能失效,连接被重置
  • 调用 $cli->close() 后连接释放,下次需重建;不调用则由协程结束时自动回收
  • 连接池更稳妥:自己封装一个 HttpClientPool,用 Channel 管理空闲 $cli,避免反复创建开销

真正难处理的不是并发本身,而是错误传播、超时分级、连接池回收时机和 fd 资源竞争——这些地方一不留神就会在线上突然卡住或内存缓慢上涨。

相关文章

精彩推荐