实例代码讲解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中字符编码简介、方法及使用建议
Jan 08 Python
Python脚本实现虾米网签到功能
Apr 12 Python
python2.7 mayavi 安装图文教程(推荐)
Jun 22 Python
用Python实现KNN分类算法
Dec 22 Python
浅谈Tensorflow由于版本问题出现的几种错误及解决方法
Jun 13 Python
Django文件上传与下载(FileFlid)
Oct 06 Python
python中with语句结合上下文管理器操作详解
Dec 19 Python
Window版下在Jupyter中编写TensorFlow的环境搭建
Apr 10 Python
Python基于模块Paramiko实现SSHv2协议
Apr 28 Python
详解pandas.DataFrame.plot() 画图函数
Jun 14 Python
Windows环境下Python3.6.8 importError: DLLload failed:找不到指定的模块
Nov 01 Python
Python中文纠错的简单实现
Jul 07 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
PHP清除字符串中所有无用标签的方法
2014/12/01 PHP
Laravel中使用FormRequest进行表单验证方法及问题汇总
2016/06/19 PHP
基于jQuery实现图片的前进与后退功能
2013/04/24 Javascript
javascript的propertyIsEnumerable()方法使用介绍
2014/04/09 Javascript
Javascript基础教程之数组 array
2015/01/18 Javascript
jQuery简单实现验证邮箱格式
2015/07/15 Javascript
基于JavaScript实现简单的随机抽奖小程序
2016/01/05 Javascript
基于JQuery的$.ajax方法进行异步请求导致页面闪烁的解决办法
2016/05/10 Javascript
动态更新highcharts数据的实现方法
2016/05/28 Javascript
jQuery多个版本和其他js库冲突的解决方法
2016/08/11 Javascript
javascript 中的事件委托详解
2016/10/25 Javascript
BootStrap 下拉菜单点击之后不会出现下拉菜单(下拉菜单不弹出)的解决方案
2016/12/14 Javascript
微信小程序 input输入框控件详解及实例(多种示例)
2016/12/14 Javascript
非常优秀的JS图片轮播插件Swiper的用法
2017/01/03 Javascript
js定时器实现倒计时效果
2017/11/05 Javascript
百度地图去掉marker覆盖物或者去掉maker的label文字方法
2018/01/26 Javascript
jQuery实现的简单对话框拖动功能示例
2018/06/05 jQuery
JavaScript实现邮箱后缀提示功能的示例代码
2018/12/13 Javascript
JS设置自定义快捷键并实现图片上下左右移动
2019/10/17 Javascript
JS原形与原型链深入详解
2020/05/09 Javascript
简单的抓取淘宝图片的Python爬虫
2014/12/25 Python
python实现将html表格转换成CSV文件的方法
2015/06/28 Python
python3.4用循环往mysql5.7中写数据并输出的实现方法
2017/06/20 Python
SVM基本概念及Python实现代码
2017/12/27 Python
python: 自动安装缺失库文件的方法
2018/10/22 Python
在Pandas中给多层索引降级的方法
2018/11/16 Python
Python3转换html到pdf的不同解决方案
2019/03/11 Python
解决django服务器重启端口被占用的问题
2019/07/26 Python
HTML5 移动页面自适应手机屏幕四类方法总结
2017/08/17 HTML / CSS
Shoes For Crews法国官网:美国领先的防滑鞋设计和制造商
2018/01/01 全球购物
Rossignol金鸡美国官网:始于1907年法国百年雪具品牌
2019/03/06 全球购物
你常见到的runtime exception
2016/09/05 面试题
2015年幼儿园大班工作总结
2015/04/25 职场文书
辩论会主持词
2015/07/03 职场文书
护士年终工作总结不会写?各科护士模板总结
2020/01/02 职场文书