python线程池 ThreadPoolExecutor 的用法示例


Posted in Python onOctober 10, 2020

前言

从Python3.2开始,标准库为我们提供了 concurrent.futures 模块,它提供了 ThreadPoolExecutor (线程池)和ProcessPoolExecutor (进程池)两个类。

相比 threading 等模块,该模块通过 submit 返回的是一个 future 对象,它是一个未来可期的对象,通过它可以获悉线程的状态主线程(或进程)中可以获取某一个线程(进程)执行的状态或者某一个任务执行的状态及返回值:

主线程可以获取某一个线程(或者任务的)的状态,以及返回值。
当一个线程完成的时候,主线程能够立即知道。
让多线程和多进程的编码接口一致。

线程池的基本使用

# coding: utf-8
from concurrent.futures import ThreadPoolExecutor
import time


def spider(page):
 time.sleep(page)
 print(f"crawl task{page} finished")
 return page

with ThreadPoolExecutor(max_workers=5) as t: # 创建一个最大容纳数量为5的线程池
 task1 = t.submit(spider, 1)
 task2 = t.submit(spider, 2) # 通过submit提交执行的函数到线程池中
 task3 = t.submit(spider, 3)

 print(f"task1: {task1.done()}") # 通过done来判断线程是否完成
 print(f"task2: {task2.done()}")
 print(f"task3: {task3.done()}")

 time.sleep(2.5)
 print(f"task1: {task1.done()}")
 print(f"task2: {task2.done()}")
 print(f"task3: {task3.done()}")
 print(task1.result()) # 通过result来获取返回值

执行结果如下:

task1: False
task2: False
task3: False
crawl task1 finished
crawl task2 finished
task1: True
task2: True
task3: False
1
crawl task3 finished

1.使用 with 语句 ,通过 ThreadPoolExecutor 构造实例,同时传入 max_workers 参数来设置线程池中最多能同时运行的线程数目。

2.使用 submit 函数来提交线程需要执行的任务到线程池中,并返回该任务的句柄(类似于文件、画图),注意 submit() 不是阻塞的,而是立即返回。

3.通过使用 done() 方法判断该任务是否结束。上面的例子可以看出,提交任务后立即判断任务状态,显示四个任务都未完成。在延时2.5后,task1 和 task2 执行完毕,task3 仍在执行中。

4.使用 result() 方法可以获取任务的返回值。

主要方法

  • wait

 wait(fs, timeout=None, return_when=ALL_COMPLETED)

wait 接受三个参数:
fs: 表示需要执行的序列
timeout: 等待的最大时间,如果超过这个时间即使线程未执行完成也将返回
return_when:表示wait返回结果的条件,默认为 ALL_COMPLETED 全部执行完成再返回

还是用上面那个例子来熟悉用法
示例:

from concurrent.futures import ThreadPoolExecutor, wait, FIRST_COMPLETED, ALL_COMPLETED
import time

def spider(page):
 time.sleep(page)
 print(f"crawl task{page} finished")
 return page

with ThreadPoolExecutor(max_workers=5) as t: 
 all_task = [t.submit(spider, page) for page in range(1, 5)]
 wait(all_task, return_when=FIRST_COMPLETED)
 print('finished')
 print(wait(all_task, timeout=2.5))

# 运行结果
crawl task1 finished
finished
crawl task2 finished
crawl task3 finished
DoneAndNotDoneFutures(done={<Future at 0x28c8710 state=finished returned int>, <Future at 0x2c2bfd0 state=finished returned int>, <Future at 0x2c1b7f0 state=finished returned int>}, not_done={<Future at 0x2c3a240 state=running>})
crawl task4 finished

1.代码中返回的条件是:当完成第一个任务的时候,就停止等待,继续主线程任务

2.由于设置了延时, 可以看到最后只有 task4 还在运行中

  • as_completed

上面虽然提供了判断任务是否结束的方法,但是不能在主线程中一直判断啊。最好的方法是当某个任务结束了,就给主线程返回结果,而不是一直判断每个任务是否结束。
ThreadPoolExecutorThreadPoolExecutor 中 的 as_completed() 就是这样一个方法,当子线程中的任务执行完后,直接用 result() 获取返回结果

用法如下:

# coding: utf-8
from concurrent.futures import ThreadPoolExecutor, as_completed
import time


def spider(page):
 time.sleep(page)
 print(f"crawl task{page} finished")
 return page

def main():
 with ThreadPoolExecutor(max_workers=5) as t:
  obj_list = []
  for page in range(1, 5):
   obj = t.submit(spider, page)
   obj_list.append(obj)

  for future in as_completed(obj_list):
   data = future.result()
   print(f"main: {data}")

# 执行结果
crawl task1 finished
main: 1
crawl task2 finished
main: 2
crawl task3 finished
main: 3
crawl task4 finished
main: 4

as_completed() 方法是一个生成器,在没有任务完成的时候,会一直阻塞,除非设置了 timeout。

当有某个任务完成的时候,会 yield 这个任务,就能执行 for 循环下面的语句,然后继续阻塞住,循环到所有的任务结束。同时,先完成的任务会先返回给主线程。

  • map

map(fn, *iterables, timeout=None)

fn: 第一个参数 fn 是需要线程执行的函数;
iterables:第二个参数接受一个可迭代对象;
timeout: 第三个参数 timeout 跟 wait() 的 timeout 一样,但由于 map 是返回线程执行的结果,如果 timeout小于线程执行时间会抛异常 TimeoutError。

用法如下:

import time
from concurrent.futures import ThreadPoolExecutor

def spider(page):
 time.sleep(page)
 return page

start = time.time()
executor = ThreadPoolExecutor(max_workers=4)

i = 1
for result in executor.map(spider, [2, 3, 1, 4]):
 print("task{}:{}".format(i, result))
 i += 1

# 运行结果
task1:2
task2:3
task3:1
task4:4

使用 map 方法,无需提前使用 submit 方法,map 方法与 python 高阶函数 map 的含义相同,都是将序列中的每个元素都执行同一个函数。

上面的代码对列表中的每个元素都执行 spider() 函数,并分配各线程池。

可以看到执行结果与上面的 as_completed() 方法的结果不同,输出顺序和列表的顺序相同,就算 1s 的任务先执行完成,也会先打印前面提交的任务返回的结果。

多线程实战

以某网站为例,演示线程池和单线程两种方式爬取的差异

# coding: utf-8
import requests
from concurrent.futures import ThreadPoolExecutor, as_completed
import time
import json
from requests import adapters

from proxy import get_proxies

headers = {
 "Host": "splcgk.court.gov.cn",
 "Origin": "https://splcgk.court.gov.cn",
 "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36",
 "Referer": "https://splcgk.court.gov.cn/gzfwww/ktgg",
}
url = "https://splcgk.court.gov.cn/gzfwww/ktgglist?pageNo=1"

def spider(page):
 data = {
  "bt": "",
  "fydw": "",
  "pageNum": page,
 }
 for _ in range(5):
  try:
   response = requests.post(url, headers=headers, data=data, proxies=get_proxies())
   json_data = response.json()
  except (json.JSONDecodeError, adapters.SSLError):
   continue
  else:
   break
 else:
  return {}

 return json_data

def main():
 with ThreadPoolExecutor(max_workers=10) as t:
  obj_list = []
  begin = time.time()
  for page in range(1, 15):
   obj = t.submit(spider, page)
   obj_list.append(obj)

  for future in as_completed(obj_list):
   data = future.result()
   print(data)
   print('*' * 50)
  times = time.time() - begin
  print(times)

if __name__ == "__main__":
 main()

运行结果:

python线程池 ThreadPoolExecutor 的用法示例

单线程实战

下面我们可以使用单线程来爬取,代码基本和上面的一样,加个单线程函数
代码如下:

# coding: utf-8
import requests
from concurrent.futures import ThreadPoolExecutor, as_completed
import time
import json
from requests import adapters

from proxy import get_proxies

headers = {
 "Host": "splcgk.court.gov.cn",
 "Origin": "https://splcgk.court.gov.cn",
 "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36",
 "Referer": "https://splcgk.court.gov.cn/gzfwww/ktgg",
}
url = "https://splcgk.court.gov.cn/gzfwww/ktgglist?pageNo=1"

def spider(page):
 data = {
  "bt": "",
  "fydw": "",
  "pageNum": page,
 }
 for _ in range(5):
  try:
   response = requests.post(url, headers=headers, data=data, proxies=get_proxies())
   json_data = response.json()
  except (json.JSONDecodeError, adapters.SSLError):
   continue
  else:
   break
 else:
  return {}

 return json_data

def single():
 begin = time.time()
 for page in range(1, 15):
  data = spider(page)
  print(data)
  print('*' * 50)

 times = time.time() - begin
 print(times)


if __name__ == "__main__":
 single()

运行结果:

python线程池 ThreadPoolExecutor 的用法示例

可以看到,总共花了 19 秒。真是肉眼可见的差距啊!如果数据量大的话,运行时间差距会更大!

以上就是python线程池 ThreadPoolExecutor 的用法示例的详细内容,更多关于python线程池 ThreadPoolExecutor 的用法及实战的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
django model去掉unique_together报错的解决方案
Oct 18 Python
在Django同1个页面中的多表单处理详解
Jan 25 Python
Python cookbook(数据结构与算法)对切片命名清除索引的方法
Mar 13 Python
Python cookbook(数据结构与算法)筛选及提取序列中元素的方法
Mar 19 Python
python如何把嵌套列表转变成普通列表
Mar 20 Python
Python 离线工作环境搭建的方法步骤
Jul 29 Python
详解Python并发编程之创建多线程的几种方法
Aug 23 Python
解决django框架model中外键不落实到数据库问题
May 20 Python
python如何快速生成时间戳
Jul 21 Python
python 基于opencv 绘制图像轮廓
Dec 11 Python
Python列表元素删除和remove()方法详解
Jan 04 Python
Python爬虫回测股票的实例讲解
Jan 22 Python
python开发一款翻译工具
Oct 10 #Python
Python pickle模块常用方法代码实例
Oct 10 #Python
Python3.9新特性详解
Oct 10 #Python
Python random模块的使用示例
Oct 10 #Python
python 装饰器的使用示例
Oct 10 #Python
python使用bs4爬取boss直聘静态页面
Oct 10 #Python
通过案例解析python鸭子类型相关原理
Oct 10 #Python
You might like
php使用ICQ网关发送手机短信
2013/10/30 PHP
php中\r \r\n \t的区别示例介绍
2014/02/08 PHP
PHP+MySQL修改记录的方法
2015/01/21 PHP
PHP中一个有趣的preg_replace函数详解
2018/08/15 PHP
关于二级域名下使用一级域名下的COOKIE的问题
2011/11/07 Javascript
javascript针对DOM的应用分析(四)
2012/04/15 Javascript
jquery插件开发之实现md5插件
2014/03/17 Javascript
javascript+canvas制作九宫格小程序
2014/12/28 Javascript
javascript实现添加附件功能的方法
2015/11/18 Javascript
JQuery移动页面开发之屏幕方向改变与滚屏的实现
2015/12/03 Javascript
详解webpack es6 to es5支持配置
2017/05/04 Javascript
Vue2.0基于vue-cli+webpack父子组件通信(实例讲解)
2017/09/14 Javascript
浅谈webpack下的AOP式无侵入注入
2017/11/12 Javascript
用p5.js制作烟花特效的示例代码
2018/03/21 Javascript
浅谈VUE中演示v-for为什么要加key
2020/01/16 Javascript
在Vue中创建可重用的 Transition的方法
2020/06/02 Javascript
vue项目中openlayers绘制行政区划
2020/12/24 Vue.js
python实现DNS正向查询、反向查询的例子
2014/04/25 Python
pandas string转dataframe的方法
2018/04/11 Python
解决python3 网络请求路径包含中文的问题
2018/05/10 Python
Python3实现的简单工资管理系统示例
2019/03/12 Python
Python json转字典字符方法实例解析
2020/04/13 Python
python实现将中文日期转换为数字日期
2020/07/14 Python
Pycharm2020.1安装无法启动问题即设置中文插件的方法
2020/08/07 Python
CSS3色彩模式有哪些?CSS3 HSL色彩模式的定义
2016/04/26 HTML / CSS
毕业生动漫设计求职信
2013/10/11 职场文书
经济管理专业毕业生推荐信
2013/11/11 职场文书
行政专员的岗位职责
2014/03/10 职场文书
教师师德师风个人整改方案
2014/09/18 职场文书
无刑事犯罪记录证明范本
2014/09/29 职场文书
班主任先进事迹材料
2014/12/17 职场文书
承诺函范文
2015/01/21 职场文书
2015年城乡环境综合治理工作总结
2015/07/24 职场文书
2019通用版导游词范本!
2019/08/07 职场文书
Java各种比较对象的方式的对比总结
2021/06/20 Java/Android
Java 垃圾回收超详细讲解记忆集和卡表
2022/04/08 Java/Android