Python多线程实战:3种高方法解决I O密集型任务
你写的爬虫程序是不是总在傻等网页加载?每次下载文件都像在排队买奶茶,明明网速不差却死活快不起来?哎,这就是典型的I/O密集型任务在折磨你的CPU!今天咱们不整虚的,直接上3个能让你程序起飞的多线程大招,包教包会,小白也能立马上手。
一、??基础版:手动开线程组队刷副本??
先来举个接地气的例子——假设你要下载10个小说章节,用普通写法就像一个人挨个点链接:
python复制import time def download(url): print(f"开始下载:{url}") time.sleep(2) # 假装在下载 print(f"下载完成:{url}") urls = [f"第{i}章" for i in range(1,11)] start = time.time() for url in urls: download(url) print(f"总耗时:{time.time()-start:.2f}秒")
这破代码跑完至少要20秒!但换成多线程就像雇了10个快递小哥同时送货:
python复制import threading threads = [] for url in urls: t = threading.Thread(target=download, args=(url,)) t.start() threads.append(t) for t in threads: t.join()
跑起来你会发现,总时间直接缩到2秒出头!原理很简单:每个线程负责一个下载任务,在等待服务器响应的空档,其他线程能接着干活。
不过这种方法有个坑——要手动管理线程数量。比如同时开1000个线程?分分钟把电脑卡成幻灯片!所以咱们得升级到...
二、??进阶版:线程池让打工人循环利用??
这就好比开个快递站,固定养着5个快递员,有活就派,没活就歇着。Python自带的ThreadPoolExecutor
简直不要太好用:
python复制from concurrent.futures import ThreadPoolExecutor with ThreadPoolExecutor(max_workers=5) as boss: tasks = [boss.submit(download, url) for url in urls] for task in tasks: task.result()
这个写法妙在哪呢?首先限制了最多5个线程,避免资源爆炸。其次用了上下文管理器(with语句),就算程序中途崩溃也不会留一堆僵尸线程。
实测处理100个下载任务,用普通线程创建要3秒,用线程池只要2.5秒。别小看这0.5秒,在服务器场景下每天能省出3小时!
更骚的操作是批量提交任务:
python复制results = boss.map(download, urls)
一行搞定所有任务分发,还能自动收集结果。不过要注意,如果某个任务抛异常,整个map会直接罢工,这时候就得用as_completed
来逐个处理。
三、??终极版:多进程+多线程组合拳??
遇到既要爬数据(I/O)又要算数据(CPU)的复杂场景怎么办?这就得请出进程和线程的黄金搭档了。举个电商数据处理的例子:
- 用多进程处理不同平台(淘宝、京东、拼多多)
- 每个进程里开多线程爬商品详情
python复制from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor def 平台总控(url列表): with ThreadPoolExecutor() as 线程池: 商品数据 = list(线程池.map(爬商品, url列表)) return 分析数据(商品数据) if __name__ == '__main__': platforms = [淘宝urls, 京东urls, 拼多多urls] with ProcessPoolExecutor() as 进程池: results = 进程池.map(平台总控, platforms)
这波操作下来,既利用多进程绕过了GIL对CPU计算的限制,又用多线程高效处理网络请求。实测某电商数据处理项目,单进程需要2小时的任务,用这个方法45分钟搞定!
不过要注意两个坑:
- ??进程间通信??别用共享变量,乖乖用Queue或数据库
- ??资源分配??要合理,通常进程数=CPU核数,每个进程线程数=3~5
??个人踩坑经验??:
刚开始学多线程那会儿,我最爱无脑开线程,结果把服务器搞崩过三次。后来才明白,处理I/O任务就像安排聚餐——线程数好比餐桌数量,人多了坐不下(资源耗尽),人少了菜凉了(性能浪费)。现在我的经验公式是:
markdown复制最佳线程数 = I/O等待时间 / (I/O等待时间 + CPU处理时间) * CPU核数 * 2
比如说某个下载任务要等2秒,处理数据0.5秒,8核CPU的话:
markdown复制(2 / 2.5) * 8 * 2 ≈ 12.8 → 取13个线程
当然这只是个参考值,具体还得实测调整。记住,多线程不是银弹,用对了是神器,用错了就是自爆按钮。新手建议先从线程池玩起,等摸清门道再上组合技,保准让你的爬虫快得飞起!