并发模型-线程模型
一、介绍
1.1 什么是线程
线程(Thread)是:
CPU 调度和执行任务的最小单位。
一个进程(Process)可以包含多个线程,这些线程共享进程的内存空间和资源,但拥有各自独立的执行栈和程序计数器。
例如:
操作系统
│
▼
Chrome(进程)
│
┌────┴────┐
│ │
▼ ▼
线程1 线程2
线程是真正执行代码的主体,而不是进程。
1.2 进程与线程的区别
| 对比项 | 进程(Process) | 线程(Thread) |
|---|---|---|
| 定义 | 资源分配的基本单位 | CPU 调度的基本单位 |
| 是否独立内存 | 是 | 否(共享进程内存) |
| 创建成本 | 高 | 较低 |
| 通信方式 | IPC(管道、Socket、共享内存等) | 共享变量、锁 |
| 崩溃影响 | 一般不影响其他进程 | 一个线程异常可能导致整个进程退出(视语言/运行时而定) |
1.3 为什么需要线程
假设没有线程:
用户A请求
↓
执行3秒
↓
用户B开始执行
所有请求只能串行处理。
有线程以后:
线程1
处理用户A
线程2
处理用户B
线程3
处理用户C
CPU 可以调度多个线程并发执行,从而提高系统吞吐能力。
1.4 项目中的线程
PHP-FPM
HTTP Request
↓
PHP-FPM Worker(进程)
↓
执行 PHP 脚本
↓
请求结束
PHP-FPM 不是线程模型,而是多进程模型,每个 Worker 一次只处理一个请求。
二、原理
2.1 CPU 如何执行线程
CPU 并不能真正同时执行无限多个线程。
它通过:
时间片(Time Slice)+ 上下文切换(Context Switch)
模拟出“多个线程同时运行”的效果。
例如:
CPU
↓
线程1(5ms)
↓
线程2(5ms)
↓
线程3(5ms)
↓
线程1(5ms)
这种高速切换让用户感觉线程是在同时运行。
2.2 什么是上下文切换(Context Switch)
当 CPU 从线程 A 切换到线程 B 时,需要保存和恢复线程状态:
- 程序计数器(PC)
- CPU 寄存器
- 栈指针
- 内核调度信息
流程:
线程A
↓
保存现场
↓
切换
↓
恢复线程B现场
↓
线程B继续执行
这个过程本身不会产生业务价值,却会消耗 CPU。
2.3 为什么线程不能无限增加
CPU 每次只能运行极少数线程,其余线程都在等待调度。
大量线程会带来:
- 上下文切换增加
- 内存占用增加
- 调度成本增加
- Cache 命中率下降
最终导致性能下降。
2.4 一个线程的成本
不同操作系统和语言实现略有不同,但一个线程通常需要数百 KB 到数 MB 的内存。 例如:
1000 个线程
≈ 数百 MB ~ 数 GB 内存
线程属于比较昂贵的系统资源。
2.5 为什么线程适合 CPU 密集型任务
线程真正发挥优势的是:
计算
加密
压缩
图像处理
视频编码
因为这些任务主要消耗 CPU,而不是等待外部资源。
如果线程大部分时间都在等待数据库、Redis 或网络返回结果,那么 CPU 实际上处于空闲状态。
这也是后来出现协程模型的重要原因。
三、实现
3.1 PHP-FPM Worker 模型
Nginx
↓
PHP-FPM Master
↓
Worker1
Worker2
Worker3
...
每个 Worker 都是独立进程:
- 一次处理一个请求
- 请求结束后等待下一个请求
因此,PHP-FPM 的并发能力主要依赖 Worker 数量。
3.2 多进程模型示意(PHP-FPM)
Nginx
│
┌────────┴────────┐
▼ ▼
Worker1 Worker2
▼ ▼
Request1 Request2
每个 Worker 之间互相隔离,一个 Worker 崩溃通常不会影响其他 Worker。