答案取决于你使用的 SAPI (Server API)编译选项。简单来说:

  1. 传统 PHP-FPM / CLI (非 ZTS)1 个主线程 + 0 个工作线程
    • 它是 单线程 的。每个请求由一个独立的进程处理,进程内部没有并发线程。
  2. Apache MPM Worker / Event (ZTS)1 个主线程 + N 个工作线程
    • 它是 多线程 的。一个进程内包含多个线程,每个线程处理一个请求。
  3. Swoole / Hyperf (Coroutine)1 个主线程 + N 个 Reactor 线程 + M 个 Worker 进程 (单线程)
    • 注意:Swoole 的 Worker 进程 本身通常是 单线程 的,但它通过 协程 (Coroutines) 在单线程内实现高并发。Swoole 的 Master 进程 是多线程的。

如果把 PHP 进程比作一家餐厅

  • PHP-FPM (单线程):是 独立小摊
    • 每个小摊(进程)只有一个厨师(线程)。
    • 客人多了,就开更多的小摊(增加 pm.max_children)。
    • 特点:隔离性好,一个摊炸了不影响别的摊;但摊位多了占地(内存)大。
  • Apache Worker (多线程):是 大型厨房
    • 一个大厨房(进程)里有多个厨师(线程)同时做菜。
    • 特点:节省空间(内存共享);但一个厨师中毒(段错误),整个厨房都得关门(进程崩溃)。
  • Swoole (协程):是 超级厨师
    • 一个厨师(Worker 进程/线程)手里同时炒 100 锅菜(协程)。
    • 他炒一下这锅,马上翻一下那锅,利用等待火候的时间(IO 等待)去处理其他锅。
    • 特点:极致的高效,单核 CPU 也能跑出高并发。

一、三种典型场景详解

1. 场景 A:PHP-FPM (最常见)
  • 架构:Multi-Process, Single-Threaded.
  • 线程数1
  • 解释
    • PHP-FPM 采用 Master-Worker 多进程模型。
    • Master 进程负责管理(监听端口、fork 子进程)。
    • Worker 进程负责处理请求。
    • 每个 Worker 进程在同一时刻只处理一个请求,且内部只有一个执行线程。
    • 并发靠 增加进程数 实现,而非线程。
  • PHP 隐喻pcntl_fork()。通过复制进程来扩容,而不是创建线程。
2. 场景 B:Apache with MPM Worker/Event (ZTS)
  • 架构:Multi-Process, Multi-Threaded.
  • 线程数1 + N (N 由 ThreadsPerChild 配置决定,通常 25-64)。
  • 解释
    • 需要编译时开启 --enable-zts (Zend Thread Safety)。
    • 一个 Apache 子进程包含多个线程。
    • 每个线程独立处理一个 PHP 请求。
    • 风险:如果某个扩展不是线程安全的 (NTS),会导致数据竞争和崩溃。因此,大多数现代 PHP 扩展默认只支持 NTS (Non-Thread-Safe)。
  • PHP 隐喻pthreads extension。真正的多线程 PHP,但生态支持差,极少用于生产环境。
3. 场景 C:Swoole / Hyperf (现代高性能)
  • 架构:Hybrid (Multi-Process Master + Multi-Reactor Threads + Single-Threaded Workers).
  • 线程数
    • Master 进程1 个主线程 + N 个 Reactor 线程 (处理网络 IO,通常等于 CPU 核数)。
    • Worker 进程1 个线程 (执行业务逻辑)。
    • Task 进程1 个线程 (执行异步任务)。
  • 关键点
    • 你的 业务代码 (Controller/Service) 运行在 Worker 进程 中。
    • Worker 进程是单线程的
    • 但是,Swoole 在单线程内通过 协程调度器 (Coroutine Scheduler) 实现了 数万级的并发
    • 所以,对于写业务代码的你来说,你依然是在“单线程”环境中,只是这个线程被极度高效地利用了。
  • PHP 隐喻Event Loop + Coroutine。单线程内的时间片轮转,而非操作系统的线程切换。

💡 核心洞察在现代 PHP (FPM/Swoole) 中,我们刻意避免使用操作系统级别的线程 (OS Threads),因为它们的上下文切换成本高,且容易引发数据竞争。我们要么用多进程 (FPM),要么用协程 (Swoole)。


二、底层原理:为什么 PHP 不喜欢多线程?

1. Zend Engine 的设计哲学
  • Share-Nothing:PHP 最初设计为 Web 脚本语言,假设每个请求都是独立的,不共享状态。
  • 全局变量:早期 PHP 大量依赖全局状态。多线程环境下,保护全局状态需要大量的锁 (Mutex),导致性能急剧下降。
  • ZTS (Zend Thread Safety)
    • 为了支持多线程,PHP 引入了 ZTS。
    • ZTS 将所有的全局变量改为 线程局部存储 (TLS, Thread Local Storage)
    • 每次访问全局变量都要通过宏 TSRMG 获取当前线程的指针,增加了开销。
    • 结果:ZTS 版本比 NTS 版本慢 10%-20%,且许多扩展不支持。
2. GIL (Global Interpreter Lock) 的缺失 vs. 存在
  • Python:有 GIL,同一时刻只能有一个线程执行字节码,多线程无法利用多核 CPU。
  • PHP没有 GIL
    • 在 ZTS 模式下,多个线程可以真正并行执行。
    • 但是,由于 PHP 扩展生态大多是非线程安全的,导致真正的多线程 PHP (如 pthreads) 难以普及。
    • Swoole 的突破:Swoole 不在 PHP 层面做多线程共享内存,而是在 C 层做网络 IO 的多线程 (Reactor),业务层依然保持单线程 (Worker),从而避开了 ZTS 的复杂性,同时获得了高并发。
3. 上下文切换成本
  • OS Thread:内核态切换,耗时微秒级,需要保存/恢复寄存器、栈指针等。
  • Coroutine:用户态切换,耗时纳秒级,只需保存少量寄存器。
  • 结论:对于 IO 密集型应用,协程比线程更高效。

三、认知牢笼:常见误区

1. 误区:“Swoole 是多线程的,所以我的代码要加锁。”
  • 真相:Swoole 的 Worker 进程 是单线程的。你的业务代码在同一时刻只有一段在执行。
  • 例外
    • 如果你使用了 Swoole\TableSwoole\Atomic,这些是跨进程/线程共享的,需要原子操作。
    • 如果你在 Worker 中创建了协程,协程之间切换时,要注意 协程上下文隔离 (不要使用全局静态变量存储请求级数据)。
  • 对策:在 Swoole Worker 中,通常不需要互斥锁 (Mutex),但需要警惕协程间的数据污染。
2. 误区:“PHP-FPM 可以通过配置变成多线程。”
  • 真相:不能。PHP-FPM 本质是多进程。你可以增加 pm.max_children 来增加并发处理能力,但每个 Child 依然是单线程。
  • 对策:如果想利用多核,就增加进程数;如果想提高单核并发,就换 Swoole/Hyperf。
3. 误区:“多线程一定比多进程快。”
  • 真相
    • CPU 密集型:多线程可能略快(共享内存,通信方便)。
    • IO 密集型:多进程 + 协程 往往更稳定、更易扩展。
    • 稳定性:多进程隔离性好,一个进程崩溃不影响其他进程;多线程一个线程崩溃可能导致整个进程退出。
  • 对策:Web 服务通常是 IO 密集型,且要求高可用,因此多进程/协程模型更受欢迎。
4. 误区:“pcntl_fork 创建的子进程是线程。”
  • 真相fork 创建的是 进程 (Process),有独立的 PID 和内存空间。线程共享内存空间。
  • 对策:区分 Process 和 Thread。PHP 原生支持多进程 (pcntl),但不原生支持多线程 (需 pthreads 扩展,且不推荐)。

🚀 总结:原子化“PHP 线程数”全景图

场景 进程模型 每个进程的线程数 并发机制 适用场景
PHP-FPM Multi-Process 1 (Single-Threaded) 多进程并行 传统 Web 应用,Laravel, Symfony
Apache Worker Multi-Threaded 1 + N (Multi-Threaded) 多线程并行 遗留系统,较少见
Swoole/Hyperf Hybrid Worker: 1
Master: 1 + N
协程 (Coroutine) 高并发 API, WebSocket, 微服务
CLI Script Single-Process 1 串行 定时任务,脚本

终极心法

PHP 线程数的本质,是“并发模型的取舍”。
别迷恋多线程的虚名,要看协程的实效。
在 PHP 的世界里,单线程 + 协程 才是王道。
于进程中见隔离,于协程见并发;以模型为尺,解线程之牛,于架构设计中,求高效之真。

行动指令

  1. 检查环境:运行 php -i | grep "Thread Safety"。如果是 disabled,你是 NTS 模式(单线程)。
  2. 理解 Swoole:如果你用 Hyperf,记住你的代码跑在单线程 Worker 中,但要小心协程上下文。
  3. 优化 FPM:如果是 FPM,调整 pm.max_children 来匹配 CPU 核数和内存,而不是寻找线程配置。
  4. 思维升级:记住,在现代 PHP 开发中,忘记“线程”这个词,拥抱“进程”和“协程”。

更多推荐