往期文章:
- 并发编程简介
- 怎样选择多线程多进程多协程
- Python速度慢的罪魁祸首,全局解释器锁GIL
- 使用多线程,Python爬虫被加速10倍
- Python实现生产者消费者爬虫
- Python线程安全问题以及解决方案
线程池概念介绍
上图左侧是展示的是一个线程的生命周期,首先,新建线程,然后准备就绪,等cpu调用,如果被调用,则开始运行,如果被切换,则又返回就绪状态,如果是因为io或者sleep,则进入阻塞状态,阻塞结束则又回到就绪状态,反反复复,直到执行完。之所以要采用线程池,右上角以说明原因。
线程池的好处:
- 提升性能:因为减去了大量新建、终止线程的开销,重用了线程资源;
- 适用场景:适合处理突发性大量请求或需要大量线程完成任务、但实际任务处理时间较短
- 防御功能:能有效避免系统因为创建线程过多,而导致系统负荷过大相应变慢等问题
- 代码优势:使用线程池的语法比自己新建线程执行线程更加简洁
线程池的使用方法
- 用法一: map函数,很简单。
```python
from concurrent.futures import ThreadPoolExecutor,as_completed
with ThreadPoolExecutor() as pool:
results = pool.map(craw,urls)
for result in results:
print(result)
> 注意map的结果和入参是顺序对应的。
- 用法二: futures模式,更强大。
```python
from concurrent.futures import ThreadPoolExecutor,as_completed
with ThreadPoolExecutor() as pool:
futures = [ pool.submit(craw,url) for url in urls]
for future in futures:
print(future.result())
for future in as_completed(futures):
print(future.result())
注意如果用as_completed顺序是不定的。
使用线程池改造爬虫程序
import concurrent.futures
import cnblogs_spider
#craw
with concurrent.futures.ThreadPoolExecutor() as pool:
htmls = pool.map(cnblogs_spider.craw,cnblogs_spider.urls)
htmls = list(zip(cnblogs_spider.urls,htmls))
for url,html in htmls:
print(url,len(html))
#parse
with concurrent.futures.ThreadPoolExecutor() as pool:
futures = {
}
for url,html in htmls:
future = pool.submit(cnblogs_spider.parse,html)
futures[future] = url
#for future,url in futures.items():
# print(url,future.result())
for future in concurrent.futures.as_completed(futures):
url = futures[future]
print(url,future.result())