文章目录

Python多进程与多线程在IO密集与CPU密集型任务的选择

发布于 2026-04-04 07:54:21 · 浏览 2 次 · 评论 0 条

Python 多进程与多线程在 IO 密集与 CPU 密集型任务的选择


当你用 Python 写程序时,如果任务跑得太慢,可能会想到“能不能同时干几件事?”——这就是并发。Python 提供了两种主要方式:多线程threading)和多进程multiprocessing)。但它们不是随便选的,选错了反而更慢。

关键区别在于:你的任务是卡在等数据(IO 密集),还是卡在算数(CPU 密集)?


第一步:判断你的任务类型

  1. 观察程序卡在哪里

    • 如果程序大部分时间在等网络响应、读写文件、数据库查询、用户输入,那就是 IO 密集型
      • 例如:下载 100 个网页、批量读取日志文件、调用多个 API。
    • 如果程序一直在做数学计算、图像处理、加密解密、排序大量数据,几乎不等待外部资源,那就是 CPU 密集型
      • 例如:计算斐波那契数列第 100000 项、对百万条数据做统计、渲染 3D 图形。
  2. 记住一个核心限制:Python 有个叫 GIL(全局解释器锁)的东西,它让同一时刻只有一个线程能执行 Python 字节码。这意味着多线程无法真正并行执行 CPU 计算


第二步:根据任务类型选择并发模型

情况一:IO 密集型任务 → 用多线程

原因:IO 操作(如 requests.get()open().read())在等待时会自动释放 GIL,其他线程就能趁机运行。所以多个线程可以“轮流等”,整体效率提升。

操作步骤

  1. 导入模块

    import threading
    import time
    import requests
  2. 定义任务函数(模拟下载网页):

    def fetch_url(url):
        response = requests.get(url)
        print(f"Downloaded {len(response.content)} bytes from {url}")
  3. 创建并启动多个线程

    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()  # **等待所有线程结束**
  4. 效果:三个请求几乎同时发出,总耗时约 1 秒(而不是串行的 3 秒)。

注意:不要用多线程处理 CPU 密集任务。比如把上面的 fetch_url 改成 time.sleep(1) 虽然也快,但换成 sum(i*i for i in range(10**7)) 就没加速效果。


情况二:CPU 密集型任务 → 用多进程

原因:每个进程有独立的 Python 解释器和内存空间,绕过 GIL 限制,真正利用多核 CPU 并行计算。

操作步骤

  1. 导入模块

    import multiprocessing
    import time
  2. 定义计算函数

    def cpu_bound_task(n):
        # 模拟高负载计算
        total = sum(i * i for i in range(n))
        return total
  3. 使用进程池执行任务

    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. 效果:在 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 任务也能用,但开销更大(进程创建、内存复制),不如多线程轻量。

第四步:特殊情况处理

  1. 混合型任务(既有 IO 又有 CPU):

    • 优先按主要瓶颈分类。如果 80% 时间在等网络,就当 IO 密集处理。
    • 或者拆分任务:用多线程处理 IO 部分,拿到数据后交给多进程池做计算。
  2. 避免过度并发

    • 不要创建上千个线程或进程。线程太多会导致切换开销;进程太多会耗尽内存。
    • IO 密集:线程数可设为 2 × CPU 核数 或更高(如 50~100)。
    • CPU 密集:进程数通常等于 CPU 核数(可通过 multiprocessing.cpu_count() 获取)。
  3. 替代方案考虑

    • 对于高并发 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())

最终决策流程

  1. 问自己:任务主要在“等”还是“算”?
  2. 如果是“等”(IO 密集)→ 用多线程异步编程
  3. 如果是“算”(CPU 密集)→ 用多进程
  4. 不确定?写两个版本跑一下时间:用 time.time() 包裹代码,实测最准。

评论 (0)

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

扫一扫,手机查看

扫描上方二维码,在手机上查看本文