4、并发与并行:多线程、多进程、异步IO的适用场景与实战对比

说到并发和并行,很多刚入行的朋友容易搞混。我简单解释一下:并发是逻辑上的同时执行,并行是物理上的同时执行。说白了,并发就像一个人同时处理几件事(来回切换),并行就像几个人各干各的活。

在实际的流程自动化中,选错并发模型,性能可能差十倍甚至百倍。我见过太多人一上来就开几百个线程,结果系统直接卡死。嗯,咱们今天就把这事彻底聊透。

4.1 多线程:I/O密集型任务的利器

多线程适合什么场景?I/O密集型任务。比如读写文件、网络请求、数据库操作。这些任务大部分时间都在等待,CPU其实很闲。

我个人习惯用Python的concurrent.futures.ThreadPoolExecutor。举个例子:

from concurrent.futures import ThreadPoolExecutor, as_completed
import requests

def fetch_url(url):
    resp = requests.get(url, timeout=5)
    return resp.status_code

urls = ["https://example.com"] * 100

with ThreadPoolExecutor(max_workers=10) as executor:
    futures = {executor.submit(fetch_url, url): url for url in urls}
    for future in as_completed(futures):
        url = futures[future]
        try:
            code = future.result()
            print(f"{url} - {code}")
        except Exception as e:
            print(f"{url} - 失败: {e}")

这里有个坑:线程数不是越多越好。我在项目中遇到过,有人把线程数设到200,结果上下文切换开销比实际干活还大。一般建议线程数 = CPU核心数 × (1 + 平均等待时间/平均计算时间)。

⚠️ 注意: Python的GIL(全局解释器锁)限制了多线程的并行能力。对于CPU密集型任务,多线程反而可能更慢。别踩这个坑。

4.2 多进程:CPU密集型任务的王牌

多进程适合CPU密集型任务。比如图像处理、数据压缩、复杂计算。每个进程有独立的GIL,可以真正利用多核CPU。

你想想看,如果你要做100张图片的缩略图生成,用多线程反而会因为GIL争抢而变慢。这时候就该上多进程了。

from multiprocessing import Pool
from PIL import Image
import os

def resize_image(filename):
    img = Image.open(filename)
    img = img.resize((800, 600))
    out_path = f"thumb_{filename}"
    img.save(out_path)
    return out_path

files = [f"img_{i}.jpg" for i in range(100)]

with Pool(processes=4) as pool:
    results = pool.map(resize_image, files)
    print(f"处理完成: {len(results)} 张")

这里要注意进程间通信的开销。我建议:能用pool.map就别手动管理进程。进程间传递大数据时,序列化/反序列化的成本很高。

💡 实战技巧: 如果任务既有I/O又有CPU计算,可以考虑混合使用。比如用多进程做计算,每个进程内部再用多线程处理I/O。我曾经在一个爬虫项目中这么干过,性能提升了3倍。

4.3 异步IO:高并发连接的终极方案

异步IO适合大量并发连接的场景。比如Web服务器、消息队列消费者、实时数据流处理。它的核心是事件循环,单线程就能处理成千上万的连接。

说白了,异步IO就是「等的时候干别的活」。Python的asyncio库是主流选择。

import asyncio
import aiohttp

async def fetch_url(session, url):
    async with session.get(url) as resp:
        return await resp.text()

async def main():
    urls = ["https://example.com"] * 100
    async with aiohttp.ClientSession() as session:
        tasks = [fetch_url(session, url) for url in urls]
        results = await asyncio.gather(*tasks)
        print(f"完成 {len(results)} 个请求")

asyncio.run(main())

为什么会这样?因为await关键字让出了控制权,事件循环可以处理其他任务。这比多线程更轻量,没有线程切换的开销。

📌 核心对比:
特性 多线程 多进程 异步IO
适用场景 I/O密集型 CPU密集型 高并发I/O
资源开销 中等
编程复杂度
数据共享 容易(需加锁) 困难(需IPC) 容易(单线程)
最大连接数 几百 几十 数万

4.4 实战选型指南

我总结了一个简单的决策流程,你可以直接套用:

  1. 先看任务类型:是I/O多还是计算多?
  2. 再看并发量:同时处理的任务有多少?
  3. 最后看复杂度:团队对异步编程熟悉吗?

举个例子:

  • 批量下载1000个文件 → 多线程(I/O密集,并发适中)
  • 实时处理10万条消息/秒 → 异步IO(高并发I/O)
  • 视频转码100个文件 → 多进程(CPU密集)
  • Web API服务 → 异步IO(高并发,低延迟)
⚠️ 避坑指南: 我曾经在一个项目中,用多线程处理大量文件读写,结果因为磁盘I/O成为瓶颈,线程数再多也没用。后来改用异步IO + 限流,问题才解决。记住:并发模型解决的是CPU等待问题,不是硬件瓶颈问题

4.5 性能对比实测

我拿一个实际案例来说明。假设我们要从100个API接口拉数据,每个请求耗时约200ms:

方案 总耗时 CPU使用率 内存占用
串行 20秒 5% 50MB
多线程(10线程) 2.1秒 15% 80MB
多进程(4进程) 2.3秒 60% 200MB
异步IO 1.8秒 10% 60MB

看到没?异步IO在资源占用和速度上都有优势。但如果你要处理的是CPU密集型任务,多进程才是王道。

💡 我的建议: 对于大多数流程自动化场景,优先考虑异步IO。如果团队不熟悉,退而求其次用多线程。多进程留给真正的计算密集型任务。记住:没有银弹,只有最适合的方案

最后说一句:别迷信某个技术。我见过有人用异步IO写了一个简单的文件复制工具,结果代码比多线程版本复杂三倍,性能还没提升。选型要务实,别为了炫技而炫技。