全部文章

并发任务处理~(多线程+多进程)标准库:concurrent.futures

在 Python 中实现并发任务时,concurrent.futures 是一个高效且简洁的标准库模块,尤其适合处理 I/O 密集型任务(如网络请求)。

concurrent.futures 通过简洁的 API 和自动化的资源管理,显著降低了并发编程的复杂度。在爬虫场景中,结合多线程并发请求,可以轻松将效率提升 5-10 倍!

concurrent.futures 的核心用法

关键参数和方法

参数/方法说明
max_workers 最大并发线程/进程数(默认线程数为 CPU 核数 * 5,进程数为 CPU 核数)
submit(fn, *args) 提交单个任务,返回 Future 对象
map(fn, iterable) 批量提交任务,按输入顺序返回结果
as_completed(fs) 返回一个生成器,按任务完成顺序产出 Future 对象
Future.result() 获取任务结果(阻塞直到任务完成)
Future.add_done_callback() 添加任务完成后的回调函数

使用线程池(ThreadPoolExecutor

from concurrent.futures import ThreadPoolExecutor, as_completed
with ThreadPoolExecutor(max_workers=5) as executor:  # 创建最多5个线程的池
    # 提交任务到线程池
    future = executor.submit(function, arg1, arg2)
    # 获取结果
    result = future.result()

批量任务示例

from concurrent.futures import ThreadPoolExecutor, as_completed
import requests

def fetch_url(url):
    response = requests.get(url)
    response.encoding = response.apparent_encoding
    # 只有return才能被future获取到
    return response.text

urls = ["https://www.baidu.com", "https://chat.deepseek.com/"]

# 方式1
with ThreadPoolExecutor(max_workers=10) as executor:
    # 提交所有任务
    executor_map = executor.map(fetch_url, urls)

    for future in executor_map:
        print(future)
# 方式2
with ThreadPoolExecutor(max_workers=2) as executor:
    # 提交所有任务
    futures = {executor.submit(fetch_url, url): url for url in urls}

    # 按完成顺序处理结果
    for future in as_completed(futures):
        url = futures[future]
        try:
            data = future.result()
            print(f"{url} 抓取成功,长度: {len(data)}")
        except Exception as e:
            print(f"{url} 抓取失败: {e}")

 

使用进程池(ProcessPoolExecutor

适用于 CPU 密集型任务(如数值计算):

from concurrent.futures import ProcessPoolExecutor

def calculate(n):
    return n * n

with ProcessPoolExecutor() as executor:
    results = executor.map(calculate, [1, 2, 3, 4])
    print(list(results))  # 输出: [1, 4, 9, 16]

 

在爬虫中的实战优化

优化前(串行)

for url in urls:
    data = requests.get(url).text
    parse(data)  # 解析数据

优化后(并行)

with ThreadPoolExecutor(max_workers=10) as executor:
    futures = [executor.submit(requests.get, url) for url in urls]
    for future in as_completed(futures):
        data = future.result().text
        parse(data)

注意事项

  1. 线程安全:确保任务函数无共享资源竞争(如写入同一文件时需加锁)。

  2. 超时控制:使用 future.result(timeout=10) 避免线程卡死。

  3. 资源限制:合理设置 max_workers,避免过多线程导致内存溢出。

为什么选择 concurrent.futures

1. 相比其他线程池的优势

特性concurrent.futures其他线程池(如 threading
API 复杂度 高层抽象,语法简洁 需要手动管理线程、锁和队列
任务调度 自动管理线程/进程池和任务队列 需手动分配任务和同步
结果处理 支持 Future 对象和回调 需自行实现结果收集
兼容性 同时支持线程和进程(统一接口) threading 仅线程,multiprocessing 仅进程
错误处理 集中捕获异常 需在每个线程中单独处理

2. 适用场景

  • I/O 密集型任务:如爬虫的网络请求、文件读写。

  • 简单并行化:需要快速将串行任务转为并行。

  • 资源管理:自动限制并发线程/进程数,避免资源耗尽。

posted @ 2025-03-26 11:17  指尖下的世界  阅读(55)  评论(0)    收藏  举报