Swoole 解决 C10K 问题的本质是单线程 + epoll 事件循环 + 协程调度,将万级连接复用在少量 Worker 进程中;传统 Apache 每连接启进程导致内存与上下文切换爆炸,而 epoll 将 I/O 就绪通知复杂度从 O(n) 降至 O(1),协程通过 Runtime Hook 将阻塞调用转为非阻塞让出,实现高并发低开销。

Swoole 解决 C10K 问题,靠的不是“更多线程”,而是用单线程 + epoll 事件循环 + 协程调度,把 1 万个连接压进一个 Worker 进程里跑 —— 这是本质,不是优化技巧。
为什么传统 Apache 模型在 C10K 场景下直接崩盘
Apache 默认每连接起一个进程(或线程),1 万个连接 ≈ 1 万个进程。每个进程至少占 2–8MB 内存,光内存就吃掉 20–80GB;更致命的是上下文切换开销:Linux 调度器每毫秒要轮询、保存、恢复上万个寄存器状态,CPU 时间全耗在调度上,真正干活的代码反而跑不动。
常见错误认知是:“加机器/升配置就能扛”。但问题不在硬件,而在模型 —— fork() 和 pthread_create() 本身就有系统调用成本,且无法横向扩展到十万级连接。
- 同步阻塞模型下,
curl_exec()、file_get_contents()、sleep()这类调用会让整个 Worker 进程卡住,所有协程一起等 - 即使改用多线程,线程栈默认 1MB,1w 连接 = 10GB 内存,且线程间锁竞争加剧
- select/poll 有数量限制(
FD_SETSIZE)或遍历开销,无法支撑高并发长连接场景(如 IM、实时推送)
epoll 是 Swoole 高并发的底层基石
Swoole 的 Reactor 线程在启动时调用 epoll_create() 创建一个内核事件表,之后所有 socket 连接都通过 epoll_ctl(EPOLL_CTL_ADD) 注册进去。它不轮询,只在内核通知“某 fd 就绪”时才唤醒对应回调 —— 时间复杂度从 O(n) 降到 O(1)。
关键点在于:一个 Reactor 线程可监听数万 fd,而 Swoole 默认只启 1 个 Reactor(可通过 reactor_num 配置多线程 Reactor,但非必须)。
-
epoll_wait()返回的是就绪事件列表,不是全部连接,无无效遍历 - 每个连接只占一个
epoll_event结构(通常 12 字节),内存占用极低 - 搭配边缘触发(ET)模式 + 非阻塞 socket,可避免重复通知和读写不全问题
- 注意:
epoll是 Linux 特有,Swoole 在 macOS 下自动 fallback 到kqueue,Windows 下用IOCP,但语义保持一致
Swoole 协程如何把“等待”变成“让出”
协程不是线程,它不抢占 CPU,也不由内核调度;它靠 Runtime Hook 把原本会阻塞的系统调用(如 socket_connect()、curl_exec())重写为异步行为:遇到 I/O 就挂起当前协程,把控制权交还事件循环,等 epoll 通知就绪后再恢复执行。
Swoole 6.1.1 是一个专为 PHP 设计的高性能事件驱动并发网络引擎。作为稳定版,它修复了编译时对 zlib 依赖的缺失及 curl 模块的内存安全风险。该版本支持协程、多线程与多进程架构,内置 TCP/HTTP/WebSocket 服务器,能够显著提升 PHP 在微服务、实时通信等场景下的执行效率与并发能力。
这要求你显式启用 hook,否则 file_get_contents() 还是会阻塞整个 Worker:
- 基础 I/O 必须开启:
Swoole\Runtime::enableCoroutine(SWOOLE_HOOK_STDIO) - 若用 cURL,补上:
SWOOLE_HOOK_CURL;若读写本地文件,加SWOOLE_HOOK_FILE - 最省事但需确认兼容性:
Swoole\Runtime::enableCoroutine(SWOOLE_HOOK_ALL) - hook 必须在 Server 启动前、任何协程创建前调用,放在
onStart里已失效
协程栈默认仅 2KB~8KB,比线程栈小两个数量级;但要注意:全局变量($GLOBALS、static、global)、超全局数组($_SERVER)仍被所有协程共享,不清理就会累积泄漏。
max_request 不是解决 C10K 的手段,而是兜底方案
max_request 是用来防内存缓慢泄漏的,不是为应对高并发设计的。它让 Worker 进程处理完指定请求数后主动退出,由 Manager 进程拉起新进程 —— 这本质是“用进程重启换内存清空”,代价是连接中断、SSL 握手重做、连接池重建。
它只适用于短连接 HTTP 服务;对长连接(WebSocket、TCP Server)、协程常驻任务(定时器、channel 监听)完全不适用 —— 因为这些场景下 Worker 本就不该退出。
真正要解决 C10K,得盯住三件事:是否用了 epoll 事件驱动、是否所有 I/O 都被协程化、协程内有没有意外持有全局引用。其他都是枝节。

















