Python 多进程与多线程在 IO 密集与 CPU 密集型任务的选择
当你用 Python 写程序时,如果任务跑得太慢,可能会想到“能不能同时干几件事?”——这就是并发。Python 提供了两种主要方式:多线程(threading)和多进程(multiprocessing)。但它们不是随便选的,选错了反而更慢。
关键区别在于:你的任务是卡在等数据(IO 密集),还是卡在算数(CPU 密集)?
第一步:判断你的任务类型
-
观察程序卡在哪里:
- 如果程序大部分时间在等网络响应、读写文件、数据库查询、用户输入,那就是 IO 密集型。
- 例如:下载 100 个网页、批量读取日志文件、调用多个 API。
- 如果程序一直在做数学计算、图像处理、加密解密、排序大量数据,几乎不等待外部资源,那就是 CPU 密集型。
- 例如:计算斐波那契数列第 100000 项、对百万条数据做统计、渲染 3D 图形。
- 如果程序大部分时间在等网络响应、读写文件、数据库查询、用户输入,那就是 IO 密集型。
-
记住一个核心限制:Python 有个叫 GIL(全局解释器锁)的东西,它让同一时刻只有一个线程能执行 Python 字节码。这意味着多线程无法真正并行执行 CPU 计算。
第二步:根据任务类型选择并发模型
情况一:IO 密集型任务 → 用多线程
原因:IO 操作(如 requests.get() 或 open().read())在等待时会自动释放 GIL,其他线程就能趁机运行。所以多个线程可以“轮流等”,整体效率提升。
操作步骤:
-
导入模块:
import threading import time import requests -
定义任务函数(模拟下载网页):
def fetch_url(url): response = requests.get(url) print(f"Downloaded {len(response.content)} bytes from {url}") -
创建并启动多个线程:
urls = [ "https://httpbin.org/delay/1", "https://httpbin.org/delay/1", "https://httpbin.org/delay/1" ] threads = [] for url in urls: t = threading.Thread(target=fetch_url, args=(url,)) threads.append(t) t.start() # **启动线程** for t in threads: t.join() # **等待所有线程结束** -
效果:三个请求几乎同时发出,总耗时约 1 秒(而不是串行的 3 秒)。
注意:不要用多线程处理 CPU 密集任务。比如把上面的
fetch_url改成time.sleep(1)虽然也快,但换成sum(i*i for i in range(10**7))就没加速效果。
情况二:CPU 密集型任务 → 用多进程
原因:每个进程有独立的 Python 解释器和内存空间,绕过 GIL 限制,真正利用多核 CPU 并行计算。
操作步骤:
-
导入模块:
import multiprocessing import time -
定义计算函数:
def cpu_bound_task(n): # 模拟高负载计算 total = sum(i * i for i in range(n)) return total -
使用进程池执行任务:
if __name__ == "__main__": numbers = [10**6, 10**6, 10**6, 10**6] # 四个大计算任务 start = time.time() with multiprocessing.Pool() as pool: results = pool.map(cpu_bound_task, numbers) # **并行执行** end = time.time() print(f"Results: {results}") print(f"Time taken: {end - start:.2f} seconds") -
效果:在 4 核 CPU 上,耗时接近单次计算的时间(理想情况下提速 4 倍)。
注意:必须把多进程代码放在
if __name__ == "__main__":下,否则在 Windows 或某些 IDE 中会无限递归创建进程。
第三步:对比性能(实测参考)
下面是在一台 4 核机器上运行两类任务的典型结果:
| 任务类型 | 方法 | 4 个任务总耗时 | 加速比 |
|---|---|---|---|
| IO 密集 | 单线程串行 | 4.0s | 1.0x |
| IO 密集 | 多线程 | 1.1s | 3.6x |
| CPU 密集 | 单线程串行 | 8.0s | 1.0x |
| CPU 密集 | 多线程 | 7.8s | 1.0x |
| CPU 密集 | 多进程 | 2.1s | 3.8x |
- 多线程对 CPU 任务几乎无加速(受 GIL 限制)。
- 多进程对 IO 任务也能用,但开销更大(进程创建、内存复制),不如多线程轻量。
第四步:特殊情况处理
-
混合型任务(既有 IO 又有 CPU):
- 优先按主要瓶颈分类。如果 80% 时间在等网络,就当 IO 密集处理。
- 或者拆分任务:用多线程处理 IO 部分,拿到数据后交给多进程池做计算。
-
避免过度并发:
- 不要创建上千个线程或进程。线程太多会导致切换开销;进程太多会耗尽内存。
- IO 密集:线程数可设为
2 × CPU 核数或更高(如 50~100)。 - CPU 密集:进程数通常等于
CPU 核数(可通过multiprocessing.cpu_count()获取)。
-
替代方案考虑:
-
对于高并发 IO(如万级连接),用
asyncio+aiohttp等异步库,比多线程更高效、内存占用更低。import asyncio import aiohttp async def fetch(session, url): async with session.get(url) as response: return await response.read() async def main(): async with aiohttp.ClientSession() as session: tasks = [fetch(session, url) for url in urls] await asyncio.gather(*tasks) asyncio.run(main())
-
最终决策流程
- 问自己:任务主要在“等”还是“算”?
- 如果是“等”(IO 密集)→ 用多线程 或 异步编程。
- 如果是“算”(CPU 密集)→ 用多进程。
- 不确定?写两个版本跑一下时间:用
time.time()包裹代码,实测最准。

暂无评论,快来抢沙发吧!