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 进程内缓存不可能存在,必须依赖外部服务来共享数据。

相关推荐
BingoGo2 天前
PHP 泛型之殇 泛型 RFC 提案被拒绝
后端·php
JaguarJack2 天前
PHP 泛型之殇 泛型 RFC 提案被拒绝
后端·php
用户3074596982072 天前
PHP 扩展——从入门到理解
php
鹏仔先生3 天前
拷贝漫画APP下载页PHP程序,后台带免费AI写作
php
LDR0063 天前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术3 天前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript
码云数智-园园3 天前
C++20 Modules 模块详解
java·开发语言·spring
swordbob3 天前
NIO的channel中什么是 fd(File Descriptor,文件描述符)
java·开发语言·nio
源分享3 天前
Java线程同步的多种实现方法(非常详细)
java·开发语言·jvm
Luminous.3 天前
C语言--day30
c语言·开发语言