并发任务处理~(多线程+多进程)标准库: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)
注意事项
-
线程安全:确保任务函数无共享资源竞争(如写入同一文件时需加锁)。
-
超时控制:使用
future.result(timeout=10)
避免线程卡死。 -
资源限制:合理设置
max_workers
,避免过多线程导致内存溢出。
为什么选择 concurrent.futures
?
1. 相比其他线程池的优势
特性 | concurrent.futures | 其他线程池(如 threading ) |
---|---|---|
API 复杂度 | 高层抽象,语法简洁 | 需要手动管理线程、锁和队列 |
任务调度 | 自动管理线程/进程池和任务队列 | 需手动分配任务和同步 |
结果处理 | 支持 Future 对象和回调 |
需自行实现结果收集 |
兼容性 | 同时支持线程和进程(统一接口) | threading 仅线程,multiprocessing 仅进程 |
错误处理 | 集中捕获异常 | 需在每个线程中单独处理 |
2. 适用场景
-
I/O 密集型任务:如爬虫的网络请求、文件读写。
-
简单并行化:需要快速将串行任务转为并行。
-
资源管理:自动限制并发线程/进程数,避免资源耗尽。