并发模型-线程模型

一、介绍

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。