实例代码讲解Python 线程池


Posted in Python onAugust 24, 2020

大家都知道当任务过多,任务量过大时如果想提高效率的一个最简单的方法就是用多线程去处理,比如爬取上万个网页中的特定数据,以及将爬取数据和清洗数据的工作交给不同的线程去处理,也就是生产者消费者模式,都是典型的多线程使用场景。

那是不是意味着线程数量越多,程序的执行效率就越快呢。

显然不是。线程也是一个对象,是需要占用资源的,线程数量过多的话肯定会消耗过多的资源,同时线程间的上下文切换也是一笔不小的开销,所以有时候开辟过多的线程不但不会提高程序的执行效率,反而会适得其反使程序变慢,得不偿失。

所以,如何确定多线程的数量是多线程编程中一个非常重要的问题。好在经过多年的摸索业界基本已形成一套默认的标准。

对于 CPU 密集型的计算场景,理论上将线程的数量设置为 CPU 核数就是最合适的,这样可以将每个 CPU 核心的性能压榨到极致,不过在工程上,线程的数量一般会设置为 CPU 核数 + 1,这样在某个线程因为未知原因阻塞时多余的那个线程完全可以顶上。

而对于 I/O 密集型的应用,就需要考虑 CPU 计算的耗时和 I/O 的耗时比了。如果 I/O 耗时和 CPU 耗时 为 1:1,那么两个线程是最合适的,因为当 A 线程做 I/O 操作时,B 线程执行 CPU 计算任务,当 B 线程做 I/O 操作时,A 线程执行 CPU 计算任务,CPU 和 I/O 的利用率都得到了百分百,完美。所以可以认为最佳线程数 = CPU 核数 * [1 +(I/O 耗时 / CPU 耗时]。

线程池

平时我们自己写多线程程序时基本都是直接调用 Thread(target=method) 即可,实际上创建线程远没有这么简单,需要分配内存,同时线程还需要调用操作系统内核的 API,然后操作系统还需要为线程分配一系列的资源,过程很是复杂,所以要尽量避免频繁的创建和销毁线程。

回想一下自己平时写多线程代码的模式,是不是当任务来临时直接创建线程,执行任务,当任务执行结束之后,线程也就随之消亡了。然后又开始循环往复。有多少个任务就创建了多少个线程。这种模式的话很浪费硬件资源。

那如何避免这种问题呢,线程池就派上用场了。

其实线程池就是生产者消费者模式的最佳实践,当线程池初始化时,会自动创建指定数量的线程,有任务到达时直接从线程池中取一个空闲线程来用即可,当任务执行结束时线程不会消亡而是直接进入空闲状态,继续等待下一个任务。而随着任务的增加线程池中的可用线程必将逐渐减少,当减少至零时,任务就需要等待了。

在 python 中使用线程池有两种方式,一种是基于第三方库 threadpool,另一种是基于 python3 新引入的库 concurrent.futures.ThreadPoolExecutor。这里我们都做一下介绍。

threadpool 方式

使用 threadpool 前需要先安装一下,看了这么久我们的文章,相信你很快就会搞定的。在命令行执行如下命令即可。

pip install threadpool

以下是一个简易的线程池使用模版,我们创建了一个函数 sayhello,然后创建了一个大小为 2 的线程池,也就是线程池总共有两个活跃线程。

最后通过 pool.putRequest() 将任务丢到线程池执, pool.wait() 等待所有线程结束。同时我们还可以定义回调函数,拿到任务的返回结果。

由结果我们可以看出,线程池中的确只有两个线程,分别为 Thread-1Thread-2

import time
import threadpool
import threading

def sayhello(name):
  print("%s say Hello to %s" % (threading.current_thread().getName(), name));
  time.sleep(1)
  return name

def callback(request, result): # 回调函数,用于取回结果
  print("callback result = %s" % result)

name_list =['admin','root','scott','tiger']
start_time = time.time()
pool = threadpool.ThreadPool(2) # 创建线程池
requests = threadpool.makeRequests(sayhello, name_list, callback) # 创建任务
[pool.putRequest(req) for req in requests] # 加入任务
pool.wait() 
print('%s cost %d second' % (threading.current_thread().getName(), time.time()-start_time))

## 运行结果如下
Thread-1 say Hello to admin
Thread-2 say Hello to root
Thread-1 say Hello to scott
Thread-2 say Hello to tiger
callback result = admin
callback result = root
callback result = tiger
callback result = scott
MainThread cost 2 second

ThreadPoolExecutor 方式

ThreadPoolExecutor 是 python3 新引入的库,具体使用方法与 threadpool 大同小异,同样是创建容量为 2 的线程池,提交四个任务。只不过这里分别是通过 submit as_completed 来提交和获取任务返回结果的。

同样由输出结果我们可以看出,两种线程池的实现方式中关于线程的命名方式是不一致的。

import time
import threading
from concurrent.futures import ThreadPoolExecutor, as_completed

def sayhello(name):
  print("%s say Hello to %s" % (threading.current_thread().getName(), name));
  time.sleep(1)
  return name

name_list =['admin','root','scott','tiger']
start_time = time.time()
with ThreadPoolExecutor(2) as executor: # 创建 ThreadPoolExecutor 
  future_list = [executor.submit(sayhello, name) for name in name_list] # 提交任务

for future in as_completed(future_list):
  result = future.result() # 获取任务结果
  print("%s get result : %s" % (threading.current_thread().getName(), result))

print('%s cost %d second' % (threading.current_thread().getName(), time.time()-start_time))

## 运行结果如下
ThreadPoolExecutor-0_0 say Hello to admin
ThreadPoolExecutor-0_1 say Hello to root
ThreadPoolExecutor-0_0 say Hello to scott
ThreadPoolExecutor-0_1 say Hello to tiger
MainThread get result : root
MainThread get result : tiger
MainThread get result : scott
MainThread get result : admin
MainThread cost 2 second

线程池总结

本文介绍了常用的两种线程池的实现方式,在多线程编程中能使用线程池就不要自己去创建线程,并不是说线程池实现的多么好,其实我们自己完全也可以实现一个功能更强大的线程池。但是其内置的线程池一来是受过全方面测试的,在安全性,性能和方便性上基本就是最优的了,同时线程池还替我们做了很多额外的工作,比如任务队列的维护,线程销毁时资源的回收等都不需要开发者去关心,我们只需注重业务逻辑即可,不需要在关心其他额外的工作,这将大大提高我们的的工作效率和使用感受。

当然其自带的线程池也不是十全十美的,至少暂时没有提供动态添加任务的入口出来。而且在设计方面不够灵活,比如我想线程池只维护一个核心数量,也就是上文说的最大数量。但是当任务过多时可以再额外创建出一些新的线程(阈值可以自定义),处理完之后这些多余的线程将自动销毁,目前这个是做不到的。

代码地址

https://github.com/JustDoPython/python-100-day/tree/master/day-053

参考资料

https://chrisarndt.de/projects/threadpool/api/

以上就是实例代码讲解Python 线程池的详细内容,更多关于Python 线程池的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
python中实现php的var_dump函数功能
Jan 21 Python
python中xrange用法分析
Apr 15 Python
Python 爬虫学习笔记之单线程爬虫
Sep 21 Python
Python基础教程之tcp socket编程详解及简单实例
Feb 23 Python
详解python中 os._exit() 和 sys.exit(), exit(0)和exit(1) 的用法和区别
Jun 23 Python
对python中使用requests模块参数编码的不同处理方法
May 18 Python
使用pycharm生成代码模板的实例
May 23 Python
python中 * 的用法详解
Jul 10 Python
在Pycharm中调试Django项目程序的操作方法
Jul 17 Python
OpenCV+face++实现实时人脸识别解锁功能
Aug 28 Python
python数据处理之如何选取csv文件中某几行的数据
Sep 02 Python
python 利用openpyxl读取Excel表格中指定的行或列教程
Feb 06 Python
详解python UDP 编程
Aug 24 #Python
PyTorch如何搭建一个简单的网络
Aug 24 #Python
Python pysnmp使用方法及代码实例
Aug 24 #Python
详解python tcp编程
Aug 24 #Python
Python rabbitMQ如何实现生产消费者模式
Aug 24 #Python
利用Python的folium包绘制城市道路图的实现示例
Aug 24 #Python
深入分析python 排序
Aug 24 #Python
You might like
Linux下将excel数据导入到mssql数据库中的方法
2010/02/08 PHP
php控制linux服务器常用功能 关机 重启 开新站点等
2012/09/05 PHP
使用Sphinx对索引进行搜索
2013/06/25 PHP
ThinkPHP V2.2说明文档没有说明的那些事实例小结
2015/07/01 PHP
实例讲解YII2中多表关联的使用方法
2017/07/21 PHP
php设计模式之装饰模式应用案例详解
2019/06/17 PHP
javascript parseInt() 函数的进制转换注意细节
2013/01/08 Javascript
20行代码实现的一个CSS覆盖率测试脚本
2013/07/07 Javascript
jquery中push()的用法(数组添加元素)
2014/11/25 Javascript
javascript操作符"!~"详解
2015/02/10 Javascript
jQuery.each使用详解
2015/07/07 Javascript
JS实现网站菜单拖拽移位效果的方法
2015/09/24 Javascript
js判断iframe中元素是否存在的实现代码
2016/12/24 Javascript
js表单序列化判断空值的实例
2017/09/22 Javascript
在vue-cli搭建的项目中增加后台mock接口的方法
2018/04/26 Javascript
详解vue.js下引入百度地图jsApi的两种方法
2018/07/27 Javascript
Vue中的$set的使用实例代码
2018/10/08 Javascript
小程序封装路由文件和路由方法(5种全解析)
2019/05/26 Javascript
js canvas实现星空连线背景特效
2019/11/01 Javascript
node.js中 mysql 增删改查操作及async,await处理实例分析
2020/02/11 Javascript
Flask框架的学习指南之开发环境搭建
2016/11/20 Python
python中字典按键或键值排序的实现代码
2019/08/27 Python
Python操作Sonqube API获取检测结果并打印过程解析
2019/11/27 Python
python使用OpenCV模块实现图像的融合示例代码
2020/04/10 Python
Html5游戏开发之乒乓Ping Pong游戏示例(二)
2013/01/21 HTML / CSS
德国BA保镖药房韩文网:kr.ba.de
2017/09/04 全球购物
Lancer Skincare官方网站:抗衰老皮肤护理
2020/11/20 全球购物
UNIX特点都有哪些
2016/04/05 面试题
国培计划培训感言
2014/03/11 职场文书
岗位说明书标准范本
2014/07/30 职场文书
爱的教育观后感
2015/06/17 职场文书
python爬虫--selenium模块
2021/03/31 Python
Django程序的优化技巧
2021/04/29 Python
CSS中Single Div 绘图技巧的实现
2021/06/18 HTML / CSS
Python中 range | np.arange | np.linspace三者的区别
2022/03/22 Python
Nginx+Tomcat负载均衡多实例详解
2022/04/11 Servers