PHP 的进程 fork 机制

PHP 的进程 fork 机制,是整个 PHP Web 运行模式的基石。

核心:Apache/Nginx 如何产生 PHP 进程

PHP 本身并不负责 fork ,fork 动作是由**与 Web Server 配合的 SAPI(Server API)**完成的。最常见的是 PHP-FPM(FastCGI Process Manager)。

1. PHP-FPM 的进程池模型

PHP-FPM 在启动时,会预先 fork 出一堆空闲的 PHP 解释器进程,等着处理请求。

复制代码
启动 PHP-FPM:
  
  Master 进程 (root)
  │
  │ fork() 系统调用
  ├── Worker 进程 1 (空闲)
  ├── Worker 进程 2 (空闲)
  ├── Worker 进程 3 (空闲)
  ├── ... (默认启动几十到几百个)
  └── Worker 进程 N (空闲)
  • Master 进程:负责管理,不处理请求。挂了就 fork 新的补上。
  • Worker 进程:真正干活的。每个 Worker 就是一个完整的、独立的 PHP 解释器实例。
2. 请求到来时的流程
复制代码
1. 用户请求 http://xxx/seckill.php
        │
        ▼
2. Nginx (Web Server)
        │ 通过 FastCGI 协议转发
        ▼
3. PHP-FPM Master 进程
        │ 从空闲进程池挑一个 Worker
        ▼
4. PHP-FPM Worker 进程 7 (PID 12345)
        │ - 这个进程已经被 fork 好了,空转着等活儿
        │ - 它有自己的独立内存空间(包括自己的堆)
        │ - 加载 seckill.php 并执行
        │ - 返回响应给 Nginx
        │ - 进程状态变回"空闲",不会销毁
        ▼
5. Worker 进程 7 继续等待下一个请求

关键点:Worker 进程是复用的(不是每个请求来都要 fork 一次新进程)。

底层:fork() 系统调用的逻辑

Linux 的 fork() 创建一个子进程,核心动作是将父进程的内存完整复制一份。

复制代码
父进程 (PID 1000)
┌────────────────────────────┐
│ 代码段                      │
│ 数据段 (stock = 100)        │
│ 堆                           │
│ 栈                           │
│ 文件描述符表                 │
└────────────────────────────┘
         │
         │ fork()
         ▼
子进程 (PID 1001)
┌────────────────────────────┐
│ 代码段 (复制)                │
│ 数据段 (stock = 100 复制)   │  ← 独立!和父进程完全隔离
│ 堆 (复制)                   │
│ 栈 (复制)                   │
│ 文件描述符表 (复制)         │
└────────────────────────────┘

写时复制(Copy-On-Write, COW)优化:

现代 OS 不会真的立刻完全复制内存,太慢了。而是:

  1. fork 刚完成时,父子进程共享同一块物理内存(只读)。
  2. 当任一进程尝试写入某块内存时,OS 才真正复制那块内存给写的那一方。

所以 PHP Worker 进程刚 fork 出来时,和父进程共享内存;一旦 Worker 开始修改 对象的 变量,OS 就为它复制一块新的,从此两者完全隔离。

完整流程一览 比如:

php 复制代码
// seckill.php
$stock = 100;  // 这个变量只在当前 Worker 进程的堆里
复制代码
Nginx 收到两个并发请求
        │
        ├── 请求1 → PHP-FPM → Worker A (PID 100)
        │                        │
        │                        ├── $stock = 100(Worker A 自己的堆)
        │                        ├── $stock-- → 变成 99
        │                        └── 返回 "成功"
        │
        └── 请求2 → PHP-FPM → Worker B (PID 101)
                                 │
                                 ├── $stock = 100(Worker B 自己的堆)← 还是 100!
                                 ├── $stock-- → 变成 99
                                 └── 返回 "成功"
        → 超卖了!因为两个 Worker 各减各的,互不影响。

这就是为什么 PHP 做秒杀只能用 Redis 之类的"外部公务员"来管理库存。

总结

步骤 谁做的 做什么
启动 PHP-FPM Master fork 出一堆 Worker 进程
处理请求 Nginx FastCGI 协议发给某个空闲 Worker
内存隔离 Linux 内核 fork() + COW 保证每个 Worker 内存独立
干掉进程 PHP-FPM Master Worker 处理过多请求后被干掉,fork 新的顶上

所以 PHP 程序员在写代码的时候,完全不需要考虑"另一个用户会不会改了我这个变量",因为每个用户的请求跑在不同的进程、不同的物理内存里。

这种"共享无"架构让 PHP 开发心智负担极低,但也正是这个特性,让纯 PHP 进程内缓存不可能存在,必须依赖外部服务来共享数据。

相关推荐
H.莓飛4 小时前
【数据结构】排序_OJ题(一):计数、桶与排序后的处理
java·开发语言·数据结构
wangchunyu1145 小时前
JAVA AQS与Lock实现原理(含CAS)
java·开发语言·word
爱喝水的鱼丶6 小时前
SAP-ABAP:ECC→S/4HANA ABAP 迁移升级指南——程序改造、语法兼容与性能优化 导语
运维·开发语言·性能优化·sap·abap·经验交流·开发交流
小灰灰搞电子6 小时前
Rust+Slint 实现FTP客户端调试助手源码分享
开发语言·后端·rust
外收内放6 小时前
面向对象高级(实战案例)
开发语言·python·学习
fanruitian7 小时前
python else用法
开发语言·python
FRing037 小时前
JavaScript入门到精通二十六(变量提升、函数剩余参数、展开运算符、ES6箭头函数)
开发语言·前端·javascript·es6·箭头函数
云深麋鹿7 小时前
C++ | 基于红黑树实现mymap与myset
开发语言·c++
ITxiaobing20238 小时前
海外广告投放的 IP 定位服务怎么选?一份基于实测数据的选型参考
网络协议·tcp/ip·php
weixin_417396838 小时前
桌面云终端PXE启动失败怎么排查?从DHCP Option到镜像下发
开发语言·前端·php·技术·酒店·综合布线·弱电