问题场景

假设我们需要处理 100 个 URL 的数据抓取任务,每个请求耗时约 0.5 秒。同步方案需要 50 秒,显然不可接受。Python 提供了三种并发方案,该如何选择?


方案一: asyncio (异步 IO)

适合 IO 密集型 任务——网络请求、文件读写、数据库查询。单线程事件循环,资源开销最小。

import asyncio
import aiohttp
import time

async def fetch_url(session, url):
    try:
        async with session.get(url, timeout=10) as resp:
            data = await resp.text()
            return {"url": url, "status": resp.status, "size": len(data)}
    except Exception as e:
        return {"url": url, "error": str(e)}

async def fetch_all(urls: list[str], concurrency: int = 10):
    semaphore = asyncio.Semaphore(concurrency)

    async def bounded_fetch(session, url):
        async with semaphore:
            return await fetch_url(session, url)

    connector = aiohttp.TCPConnector(limit=concurrency)
    async with aiohttp.ClientSession(connector=connector) as session:
        tasks = [bounded_fetch(session, u) for u in urls]
        return await asyncio.gather(*tasks)

async def main():
    urls = [f"https://httpbin.org/delay/0.5?n={i}" for i in range(100)]
    start = time.perf_counter()
    results = await fetch_all(urls, concurrency=20)
    elapsed = time.perf_counter() - start
    print(f"100 requests in {elapsed:.1f}s")  # ~2.5s

asyncio.run(main())

关键点: - Semaphore 控制并发上限,避免打爆目标服务器 - aiohttp 代替 requests——后者是同步库,会阻塞事件循环 - 单线程无 GIL 竞争,适合大量 IO 等待


方案二: threading (多线程)

适合 IO 密集型但无法用 async 的场景——调用不支持 async 的第三方库。

from concurrent.futures import ThreadPoolExecutor, as_completed
import requests
import time

def fetch_sync(url: str) -> dict:
    try:
        resp = requests.get(url, timeout=10)
        return {"url": url, "status": resp.status_code, "size": len(resp.text)}
    except Exception as e:
        return {"url": url, "error": str(e)}

def fetch_with_threads(urls: list[str], workers: int = 20):
    start = time.perf_counter()
    results = []
    with ThreadPoolExecutor(max_workers=workers) as executor:
        future_map = {executor.submit(fetch_sync, u): u for u in urls}
        for future in as_completed(future_map):
            results.append(future.result())
    elapsed = time.perf_counter() - start
    print(f"100 requests in {elapsed:.1f}s")
    return results

线程安全陷阱:

# 错误: 线程间共享可变状态不加锁
counter = 0

def unsafe_increment():
    global counter
    for _ in range(100_000):
        counter += 1  # 非原子操作!

# 正确: 使用 Lock
from threading import Lock
lock = Lock()
safe_counter = 0

def safe_increment():
    global safe_counter
    for _ in range(100_000):
        with lock:
            safe_counter += 1

方案三: multiprocessing (多进程)

适合 CPU 密集型 任务——图像处理、数据计算、机器学习推理。绕过 GIL,真正利用多核。

from concurrent.futures import ProcessPoolExecutor
import hashlib
import time

def cpu_intensive(n: int) -> dict:
    data = b"Python concurrent programming " * 10_000
    start = time.perf_counter()
    for _ in range(n):
        hashlib.sha256(data).hexdigest()
    elapsed = time.perf_counter() - start
    return {"task": n, "elapsed": f"{elapsed:.2f}s"}

def run_cpu_tasks():
    workloads = [500, 500, 500, 500, 500, 500, 500, 500]
    start = time.perf_counter()
    with ProcessPoolExecutor(max_workers=4) as executor:
        results = list(executor.map(cpu_intensive, workloads))
    total = time.perf_counter() - start
    print(f"8 tasks / 4 processes: {total:.1f}s")
    return results

if __name__ == "__main__":
    run_cpu_tasks()

选型速查表

场景 方案 理由
网络请求、数据库 IO asyncio 单线程低开销,协程切换极快
调用不支持 async 的 IO 库 threading 线程池兼容性好
CPU 密集计算 multiprocessing 绕过 GIL,真正并行
少量 IO + 计算混合 threading + multiprocessing 按任务类型拆分
微服务高并发 API asyncio + FastAPI 原生异步支持

核心原则: IO 密集选 async,CPU 密集选多进程,混用场景先 profiling 再优化。