python线程池如何使用


Posted in Python onMay 28, 2020

线程池的使用

线程池的基类是 concurrent.futures 模块中的 Executor,Executor 提供了两个子类,即 ThreadPoolExecutor 和ProcessPoolExecutor,其中 ThreadPoolExecutor 用于创建线程池,而 ProcessPoolExecutor 用于创建进程池。

如果使用线程池/进程池来管理并发编程,那么只要将相应的 task 函数提交给线程池/进程池,剩下的事情就由线程池/进程池来搞定。

Exectuor 提供了如下常用方法:

  • submit(fn, *args, **kwargs):将 fn 函数提交给线程池。*args 代表传给 fn 函数的参数,*kwargs 代表以关键字参数的形式为 fn 函数传入参数。
  • map(func, *iterables, timeout=None, chunksize=1):该函数类似于全局函数 map(func, *iterables),只是该函数将会启动多个线程,以异步方式立即对 iterables 执行 map 处理。
  • shutdown(wait=True):关闭线程池。

程序将 task 函数提交(submit)给线程池后,submit 方法会返回一个 Future 对象,Future 类主要用于获取线程任务函数的返回值。由于线程任务会在新线程中以异步方式执行,因此,线程执行的函数相当于一个“将来完成”的任务,所以 Python 使用 Future 来代表。

实际上,在 Java 的多线程编程中同样有 Future,此处的 Future 与 Java 的 Future 大同小异。

Future 提供了如下方法:

  • cancel():取消该 Future 代表的线程任务。如果该任务正在执行,不可取消,则该方法返回 False;否则,程序会取消该任务,并返回 True。
  • cancelled():返回 Future 代表的线程任务是否被成功取消。
  • running():如果该 Future 代表的线程任务正在执行、不可被取消,该方法返回 True。
  • done():如果该 Funture 代表的线程任务被成功取消或执行完成,则该方法返回 True。
  • result(timeout=None):获取该 Future 代表的线程任务最后返回的结果。如果 Future 代表的线程任务还未完成,该方法将会阻塞当前线程,其中 timeout 参数指定最多阻塞多少秒。
  • exception(timeout=None):获取该 Future 代表的线程任务所引发的异常。如果该任务成功完成,没有异常,则该方法返回 None。
  • add_done_callback(fn):为该 Future 代表的线程任务注册一个“回调函数”,当该任务成功完成时,程序会自动触发该 fn 函数。

在用完一个线程池后,应该调用该线程池的 shutdown() 方法,该方法将启动线程池的关闭序列。调用 shutdown() 方法后的线程池不再接收新任务,但会将以前所有的已提交任务执行完成。当线程池中的所有任务都执行完成后,该线程池中的所有线程都会死亡。

使用线程池来执行线程任务的步骤如下:

a、调用 ThreadPoolExecutor 类的构造器创建一个线程池。

b、定义一个普通函数作为线程任务。

c、调用 ThreadPoolExecutor 对象的 submit() 方法来提交线程任务。

d、当不想提交任何任务时,调用 ThreadPoolExecutor 对象的 shutdown() 方法来关闭线程池。

下面程序示范了如何使用线程池来执行线程任务:

from concurrent.futures import ThreadPoolExecutor
import threading
import time
# 定义一个准备作为线程任务的函数
def action(max):
  my_sum = 0
  for i in range(max):
    print(threading.current_thread().name + ' ' + str(i))
    my_sum += i
  return my_sum
# 创建一个包含2条线程的线程池
pool = ThreadPoolExecutor(max_workers=2)
# 向线程池提交一个task, 50会作为action()函数的参数
future1 = pool.submit(action, 50)
# 向线程池再提交一个task, 100会作为action()函数的参数
future2 = pool.submit(action, 100)
# 判断future1代表的任务是否结束
print(future1.done())
time.sleep(3)
# 判断future2代表的任务是否结束
print(future2.done())
# 查看future1代表的任务返回的结果
print(future1.result())
# 查看future2代表的任务返回的结果
print(future2.result())
# 关闭线程池
pool.shutdown()

上面程序中,第 13 行代码创建了一个包含两个线程的线程池,接下来的两行代码只要将 action() 函数提交(submit)给线程池,该线程池就会负责启动线程来执行 action() 函数。这种启动线程的方法既优雅,又具有更高的效率。

当程序把 action() 函数提交给线程池时,submit() 方法会返回该任务所对应的 Future 对象,程序立即判断 futurel 的 done() 方法,该方法将会返回 False(表明此时该任务还未完成)。接下来主程序暂停 3 秒,然后判断 future2 的 done() 方法,如果此时该任务已经完成,那么该方法将会返回 True。

程序最后通过 Future 的 result() 方法来获取两个异步任务返回的结果。

读者可以自己运行此代码查看运行结果,这里不再演示。

当程序使用 Future 的 result() 方法来获取结果时,该方法会阻塞当前线程,如果没有指定 timeout 参数,当前线程将一直处于阻塞状态,直到 Future 代表的任务返回。

获取执行结果

前面程序调用了 Future 的 result() 方法来获取线程任务的运回值,但该方法会阻塞当前主线程,只有等到钱程任务完成后,result() 方法的阻塞才会被解除。

如果程序不希望直接调用 result() 方法阻塞线程,则可通过 Future 的 add_done_callback() 方法来添加回调函数,该回调函数形如 fn(future)。当线程任务完成后,程序会自动触发该回调函数,并将对应的 Future 对象作为参数传给该回调函数。

下面程序使用 add_done_callback() 方法来获取线程任务的返回值:

from concurrent.futures import ThreadPoolExecutor
import threading
import time
# 定义一个准备作为线程任务的函数
def action(max):
  my_sum = 0
  for i in range(max):
    print(threading.current_thread().name + ' ' + str(i))
    my_sum += i
  return my_sum
# 创建一个包含2条线程的线程池
with ThreadPoolExecutor(max_workers=2) as pool:
  # 向线程池提交一个task, 50会作为action()函数的参数
  future1 = pool.submit(action, 50)
  # 向线程池再提交一个task, 100会作为action()函数的参数
  future2 = pool.submit(action, 100)
  def get_result(future):
    print(future.result())
  # 为future1添加线程完成的回调函数
  future1.add_done_callback(get_result)
  # 为future2添加线程完成的回调函数
  future2.add_done_callback(get_result)
  print('--------------')

上面主程序分别为 future1、future2 添加了同一个回调函数,该回调函数会在线程任务结束时获取其返回值。

主程序的最后一行代码打印了一条横线。由于程序并未直接调用 future1、future2 的 result() 方法,因此主线程不会被阻塞,可以立即看到输出主线程打印出的横线。接下来将会看到两个新线程并发执行,当线程任务执行完成后,get_result() 函数被触发,输出线程任务的返回值。

另外,由于线程池实现了上下文管理协议(Context Manage Protocol),因此,程序可以使用 with 语句来管理线程池,这样即可避免手动关闭线程池,如上面的程序所示。

此外,Exectuor 还提供了一个 map(func, *iterables, timeout=None, chunksize=1) 方法,该方法的功能类似于全局函数 map(),区别在于线程池的 map() 方法会为 iterables 的每个元素启动一个线程,以并发方式来执行 func 函数。这种方式相当于启动 len(iterables) 个线程,井收集每个线程的执行结果。

例如,如下程序使用 Executor 的 map() 方法来启动线程,并收集线程任务的返回值:

from concurrent.futures import ThreadPoolExecutor
import threading
import time
# 定义一个准备作为线程任务的函数
def action(max):
  my_sum = 0
  for i in range(max):
    print(threading.current_thread().name + ' ' + str(i))
    my_sum += i
  return my_sum
# 创建一个包含4条线程的线程池
with ThreadPoolExecutor(max_workers=4) as pool:
  # 使用线程执行map计算
  # 后面元组有3个元素,因此程序启动3条线程来执行action函数
  results = pool.map(action, (50, 100, 150))
  print('--------------')
  for r in results:
print(r)

上面程序使用 map() 方法来启动 3 个线程(该程序的线程池包含 4 个线程,如果继续使用只包含两个线程的线程池,此时将有一个任务处于等待状态,必须等其中一个任务完成,线程空闲出来才会获得执行的机会),map() 方法的返回值将会收集每个线程任务的返回结果。

运行上面程序,同样可以看到 3 个线程并发执行的结果,最后通过 results 可以看到 3 个线程任务的返回结果。

通过上面程序可以看出,使用 map() 方法来启动线程,并收集线程的执行结果,不仅具有代码简单的优点,而且虽然程序会以并发方式来执行 action() 函数,但最后收集的 action() 函数的执行结果,依然与传入参数的结果保持一致。也就是说,上面 results 的第一个元素是 action(50) 的结果,第二个元素是 action(100) 的结果,第三个元素是 action(150) 的结果。

实例扩展:

# coding:utf-8
  
import Queue
import threading
import sys
import time
import math
  
  
class WorkThread(threading.Thread):
  
  def __init__(self, task_queue):
    threading.Thread.__init__(self)
    self.setDaemon(True)
    self.task_queue = task_queue
    self.start()
    self.idle = True
  
  def run(self):
    sleep_time = 0.01 # 第1次无任务可做时休息10毫秒
    multiply = 0
    while True:
      try:
        # 从队列中取一个任务
        func, args, kwargs = self.task_queue.get(block=False)
        self.idle = False
        multiply = 0
        # 执行之
        func(*args, **kwargs)
      except Queue.Empty:
        time.sleep(sleep_time * math.pow(2, multiply))
        self.idle = True
        multiply += 1
        continue
      except:
        print sys.exc_info()
        raise
  
  
class ThreadPool:
  
  def __init__(self, thread_num=10, max_queue_len=1000):
    self.max_queue_len = max_queue_len
    self.task_queue = Queue.Queue(max_queue_len) # 任务等待队列
    self.threads = []
    self.__create_pool(thread_num)
  
  def __create_pool(self, thread_num):
    for i in xrange(thread_num):
      thread = WorkThread(self.task_queue)
      self.threads.append(thread)
  
  def add_task(self, func, *args, **kwargs):
    '''添加一个任务,返回任务等待队列的长度
      调用该方法前最后先调用isSafe()判断一下等待的任务是不是很多,以防止提交的任务被拒绝
    '''
    try:
      self.task_queue.put((func, args, kwargs))
    except Queue.Full:
      raise # 队列已满时直接抛出异常,不给执行
    return self.task_queue.qsize()
  
  def isSafe(self):
    '''等待的任务数量离警界线还比较远
    '''
    return self.task_queue.qsize() < 0.9 * self.max_queue_len
  
  def wait_for_complete(self):
    '''等待提交到线程池的所有任务都执行完毕
    '''
    #首先任务等待队列要变成空
    while not self.task_queue.empty():
      time.sleep(1)
    # 其次,所以计算线程要变成idle状态
    while True:
      all_idle = True
      for th in self.threads:
        if not th.idle:
          all_idle = False
          break
      if all_idle:
        break
      else:
        time.sleep(1)
  
  
if __name__ == '__main__':
  def foo(a, b):
    print a + b
    time.sleep(0.01)
  thread_pool = ThreadPool(10, 100)
  '''在Windows上测试不通过,Windows上Queue.Queue不是线程安全的'''
  size = 0
  for i in xrange(10000):
    try:
      size = thread_pool.add_task(foo, i, 2 * i)
    except Queue.Full:
      print 'queue full, queue size is ', size
  time.sleep(2)

到此这篇关于python线程池如何使用的文章就介绍到这了,更多相关python中的线程池详解内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Python 相关文章推荐
Python操作列表的常用方法分享
Feb 13 Python
Python实现把回车符\r\n转换成\n
Apr 23 Python
Centos Python2 升级到Python3的简单实现
Jun 21 Python
利用numpy+matplotlib绘图的基本操作教程
May 03 Python
django框架自定义用户表操作示例
Aug 07 Python
python中文编码与json中文输出问题详解
Aug 24 Python
python 实现UTC时间加减的方法
Dec 31 Python
基于Keras 循环训练模型跑数据时内存泄漏的解决方式
Jun 11 Python
Django vue前后端分离整合过程解析
Nov 20 Python
python反爬虫方法的优缺点分析
Nov 25 Python
简单谈谈Python面向对象的相关知识
Jun 28 Python
Python Pygame实战在打砖块游戏的实现
Mar 17 Python
python中前缀运算符 *和 **的用法示例详解
May 28 #Python
PHP基于phpqrcode类库生成二维码过程解析
May 28 #Python
Python函数参数分类原理详解
May 28 #Python
pygame用blit()实现动画效果的示例代码
May 28 #Python
PyCharm中如何直接使用Anaconda已安装的库
May 28 #Python
Python内置异常类型全面汇总
May 28 #Python
python不到50行代码完成了多张excel合并的实现示例
May 28 #Python
You might like
自定义php类(查找/修改)xml文档
2013/03/26 PHP
PHP中mysqli_affected_rows作用行数返回值分析
2014/12/26 PHP
PHP生成可点击刷新的验证码简单示例
2016/05/13 PHP
jquery判断单个复选框是否被选中的代码
2009/09/03 Javascript
javascript中的prototype属性实例分析说明
2010/08/09 Javascript
jQuery EasyUI API 中文文档 - TreeGrid 树表格使用介绍
2011/11/21 Javascript
JS判断对象是否存在的10种方法总结
2013/12/23 Javascript
jquery下拉select控件操作方法分享(jquery操作select)
2014/03/25 Javascript
JS实现选中当前菜单后高亮显示的导航条效果
2015/10/15 Javascript
JS采用绝对定位实现回到顶部效果完整实例
2016/06/20 Javascript
JQuery为元素添加样式的实现方法
2016/07/20 Javascript
jQuery实现遮罩层登录对话框
2016/12/29 Javascript
Javascript前端经典的面试题及答案
2017/03/14 Javascript
JS之if语句对接事件动作逻辑(详解)
2017/06/28 Javascript
JS对象序列化成json数据和json数据转化为JS对象的代码
2017/08/23 Javascript
js实现动态添加上传文件页面
2018/10/22 Javascript
微信小程序绘制图片发送朋友圈
2019/07/25 Javascript
微信小程序自定义头部导航栏(组件化)
2019/11/15 Javascript
微信小程序点击view动态添加样式过程解析
2020/01/21 Javascript
es6函数之rest参数用法实例分析
2020/04/18 Javascript
Python linecache.getline()读取文件中特定一行的脚本
2008/09/06 Python
详谈python http长连接客户端
2017/06/12 Python
pyqt5 删除layout中的所有widget方法
2019/06/25 Python
Python Pandas 获取列匹配特定值的行的索引问题
2019/07/01 Python
python中shell执行知识点
2020/05/06 Python
Django生成数据库及添加用户报错解决方案
2020/10/09 Python
床上用品全球在线购物:BeddingInn
2016/12/18 全球购物
俄罗斯一家时尚女装商店:Charuel
2019/12/04 全球购物
好邻里事迹材料
2014/01/16 职场文书
《诚实与信任》教学反思
2014/04/10 职场文书
诚信考试倡议书
2014/04/15 职场文书
交通安全横幅标语
2014/10/07 职场文书
广播稿:校园广播稿范文
2019/04/17 职场文书
vue-cropper插件实现图片截取上传组件封装
2021/05/27 Vue.js
详解Spring Boot使用系统参数表提升系统的灵活性
2021/06/30 Java/Android
Apache POI的基本使用详解
2021/11/07 Servers