Python 中线程和进程有什么区别?什么时候用多线程,什么时候用多进程?
简化版
线程是同一进程内的执行流,共享内存,创建和切换成本较低,适合 I/O 密集任务;进程有独立内存空间,隔离性更好,成本更高,更适合 CPU 密集任务。由于 CPython 有 GIL,多线程通常不能让纯 Python CPU 密集代码真正并行,多进程更适合利用多核 CPU。
详细版
线程和进程可以从几个角度比较:
| 维度 | 线程 | 进程 |
|---|---|---|
| 内存空间 | 同一进程内线程共享内存 | 每个进程通常有独立地址空间 |
| 创建成本 | 较低 | 较高 |
| 通信方式 | 共享变量、Queue、锁等 | Queue、Pipe、共享内存、文件、Socket 等 |
| 崩溃影响 | 一个线程崩溃可能影响整个进程 | 一个子进程崩溃通常更容易隔离 |
| 适合任务 | 网络请求、文件 I/O、数据库 I/O | 图像处理、压缩、计算、模型推理等 CPU 密集任务 |
典型选择:
- I/O 密集:多线程或异步 I/O;
- CPU 密集:多进程,或交给释放 GIL 的 C 扩展、NumPy、外部服务;
- 需要隔离:多进程;
- 需要共享大量内存且逻辑简单:线程可能更方便,但必须处理同步问题。
示例:
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
def io_task(url):
return fetch(url)
def cpu_task(n):
return sum(i * i for i in range(n))
with ThreadPoolExecutor(max_workers=20) as pool:
results = list(pool.map(io_task, urls))
with ProcessPoolExecutor(max_workers=4) as pool:
results = list(pool.map(cpu_task, numbers))
面试时要说清:并发是同时处理多个任务,并行是同一时刻真正多个任务一起执行;Python 线程能提升 I/O 并发,但纯 Python CPU 并行通常要靠多进程。
完整版教学
一、先区分并发和并行
并发强调“同时处理多个任务”,并不要求同一时刻真的多个任务都在 CPU 上运行。一个线程等待网络返回时,另一个线程可以继续做别的事,这就是并发价值。
并行强调“同一时刻多个任务真正一起执行”,通常需要多核 CPU 或多台机器。
Python 面试里很多问题绕来绕去,其实都在问:你的任务到底是 I/O 等待多,还是 CPU 计算多?
二、线程为什么适合 I/O 密集任务
I/O 密集任务的大量时间花在等待外部资源:
- 等网络响应;
- 等数据库返回;
- 等磁盘读写;
- 等第三方接口。
等待期间 CPU 是空的。多线程可以让一个线程等待 I/O 时,另一个线程继续运行。
from concurrent.futures import ThreadPoolExecutor
def download(url):
return request_url(url)
with ThreadPoolExecutor(max_workers=20) as pool:
for content in pool.map(download, urls):
handle(content)
即使存在 GIL,I/O 等待期间线程通常会释放执行机会,所以多线程仍然能显著提升吞吐。
三、进程为什么适合 CPU 密集任务
CPU 密集任务主要消耗 CPU,比如:
- 大量数学计算;
- 图片压缩;
- 视频转码;
- 纯 Python 循环计算;
- 加密解密、解析大量数据。
在 CPython 中,GIL 会让同一进程内多个 Python 线程执行字节码时互相竞争。纯 Python CPU 密集任务用多线程不一定更快,甚至可能因为线程切换更慢。
多进程有独立解释器和独立 GIL,可以更好利用多核:
from concurrent.futures import ProcessPoolExecutor
def compute(n):
return sum(i * i for i in range(n))
with ProcessPoolExecutor(max_workers=4) as pool:
results = list(pool.map(compute, [10_000_000] * 8))
四、共享内存是优点也是风险
线程共享同一进程内的对象,传数据很方便:
items.append(value)
但共享也意味着竞争。多个线程同时修改同一个对象,可能导致数据错乱,需要 Lock、Queue 等同步工具。
进程之间默认不共享普通对象,隔离性更好,但通信成本更高。传给子进程的数据通常需要序列化,这对大对象会有明显开销。
五、工程中怎么做选择
可以按这个顺序判断:
- 任务主要在等外部资源吗?优先线程池或异步 I/O;
- 任务主要在吃 CPU 吗?优先进程池;
- 是否要强隔离,避免某个任务拖垮主进程?倾向进程;
- 是否需要频繁共享大量状态?线程更方便,但同步复杂;
- 是否已经使用 NumPy、Pandas 等释放 GIL 的 C 扩展?线程也可能有效。
这个判断比背“线程轻、进程重”更有面试价值。
六、常见误区与追问
| 维度 | 线程 | 进程 |
|---|---|---|
| 地址空间 | 同进程内共享 | 默认相互隔离 |
| 切换和创建成本 | 较低 | 较高 |
| CPython CPU 并行 | 受 GIL 限制 | 可利用多核 |
| 适合任务 | I/O 密集、等待多 | CPU 密集、隔离要求高 |
- 误区:并发和并行是同一个概念。 并发强调任务交替推进,并行强调同一时刻真的同时执行;单核也能并发,多核才更容易并行。
- 误区:Python 线程完全没有价值。 I/O 阻塞期间线程可以切换,网络请求、文件等待、数据库调用等场景仍可能明显受益。
- 误区:进程一定适合所有高并发任务。 进程隔离好但成本高,海量短 I/O 任务用进程可能浪费内存和调度资源。
- 追问:为什么 CPU 密集任务常用多进程? CPython 的 GIL 限制同一进程内多个 Python 线程同时执行字节码,多进程能让多个解释器进程分布到多核。
- 追问:线程共享内存是优点还是风险? 两者都是:共享让通信简单,但共享可变状态需要锁,否则容易竞态。
- 追问:工程上如何回答选择题? 先判断任务瓶颈是等待 I/O 还是消耗 CPU,再看数据共享、隔离、启动成本和部署资源。
记忆钩子:线程省成本、适合等 I/O;进程隔离强、适合吃 CPU;别把“并发”和“并行”混着答。
七、加强记忆
线程轻、共享内存、适合 I/O;进程重、隔离性强、适合 CPU。Python 里还要补上 GIL 这个限制:多线程不是纯 Python CPU 密集任务的多核加速器,但对 I/O 并发依然很有用;要跑满多核,通常选择多进程或释放 GIL 的底层扩展。