系统启动一个新线程的成本是比较高的,因为它涉及与操作系统的交互。在这种情形下,使用线程池可以很好地提升性能,尤其是当程序中需要创建大量生存期很短暂的线程时,更应该考虑使用线程池。

线程池在系统启动时即创建大量空闲的线程,程序只要将一个函数提交给线程池,线程池就会启动一个空闲的线程来执行它。当该函数执行结束后,该线程并不会死亡,而是再次返回到线程池中变成空闲状态,等待执行下一个函数。

此外,使用线程池可以有效地控制系统中并发线程的数量。当系统中包含有大量的并发线程时,会导致系统性能急剧下降,甚至导致 Python 解释器崩溃,而线程池的最大线程数参数可以控制系统中并发线程的数量不超过此数。

线程池的使用

线程池的基类是 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() 方法后的线程池不再接收新任务,但会将以前所有的已提交任务执行完成。当线程池中的所有任务都执行完成后,该线程池中的所有线程都会死亡。

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

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

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

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

当不想提交任何任务时,调用 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) 的结果。

python关闭线程池_python线程池相关推荐

  1. python 线程池_Python线程池及其原理和使用(超级详细)

    系统启动一个新线程的成本是比较高的,因为它涉及与操作系统的交互.在这种情形下,使用线程池可以很好地提升性能,尤其是当程序中需要创建大量生存期很短暂的线程时,更应该考虑使用线程池. 线程池在系统启动时即 ...

  2. python 线程池_python线程池

    线程池概念 什么是线程池? 诸如web服务器.数据库服务器.文件服务器和邮件服务器等许多服务器应用都面向处理来自某些远程来源的大量短小的任务. 构建服务器应用程序的一个过于简单的模型是:每当一个请求到 ...

  3. python 线程退出_python线程退出

    广告关闭 腾讯云11.11云上盛惠 ,精选热门产品助力上云,云服务器首年88元起,买的越多返的越多,最高返5000元! 如果某线程并未使用很多 io 操作, 它会在自己的时间片内一直占用处理器(和 g ...

  4. python 判断线程状态_Python 线程和进程

    前言 学编程,谁没有为线程折腾过啊. 目录 线程与进程 线程与进程是操作系统里面的术语,简单来讲,每一个应用程序都有一个自己的进程. 操作系统会为这些进程分配一些执行资源,例如内存空间等. 在进程中, ...

  5. python 判断线程状态_Python线程指南

    Python线程指南 本文介绍了Python对于线程的支持,包括"学会"多线程编程需要掌握的基础以及Python两个线程标准库的完整介绍及使用示例. 注意:本文基于Python2. ...

  6. python线程状态_Python线程

    1. 线程基础 1.1. 线程状态 线程有5种状态,状态转换的过程如下图所示: 1.2. 线程同步(锁) 多线程的优势在于可以同时运行多个任务(至少感觉起来是这样).但是当线程需要共享数据时,可能存在 ...

  7. python进程池_python进程池剖析(一)

    python中两个常用来处理进程的模块分别是subprocess和multiprocessing,其中subprocess通常用于执行外部程序,比如一些第三方应用程序,而不是Python程序.如果需要 ...

  8. python结束线程类_Python线程指南(转)

    1. 线程基础 1.1. 线程状态 线程有5种状态,状态转换的过程如下图所示: 1.2. 线程同步(锁) 多线程的优势在于可以同时运行多个任务(至少感觉起来是这样).但是当线程需要共享数据时,可能存在 ...

  9. python 线程同步_python线程同步(2)

    Barrier 有人翻译成栅栏,建议使用屏障,可以想象成路障,道闸. 3.2引入python的新功能. Barrier(parties,action  = None,timeout = None):构 ...

  10. python线程进程_python 线程进程

    四 线程死锁和递归锁 在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁,因为系统判断这部分资源都正在使用,所有这两个线程在无外力作用下将一直等待下去.下面 ...

最新文章

  1. 暑期集训2:ACM基础算法 例2:POJ-2456
  2. SuperGLUE和GLUE双料冠军!图灵T-NLRv5:MNLI和RTE首次达到人类水平
  3. PostMessage
  4. 通过ribbon 根据服务名获取所有服务实例的IP和端口列表
  5. 2020新年发红包Java实现
  6. IP,IP地址,mac地址
  7. 实战篇:教你建设企业销售分析系统
  8. 关于 数据库 my_slq的 安装及其卸载
  9. 前端相关书籍(转摘)
  10. S变换在特征提取中的使用
  11. Delphi2010及注册码下载地址
  12. SpringBoot 通过阿里云的短信功能 实现发送手机验证码
  13. Nginx和HA负载
  14. 电路中的正弦信号 Sin
  15. 如何将鼠标和键盘连接到PlayStation 4
  16. win10:强力删除文件
  17. C# WPF MVVM框架搭建
  18. GPL amp; LGPL
  19. C语言教程:十进制转换任意进制
  20. Cocos 引擎助力游戏开发者突围

热门文章

  1. 华为机试在线训练|解题记录|HJ01-103
  2. 计算机组成原理同步测试,计算机组成原理白中英单元练习题
  3. 怎么下载python的各种库_各种Python库安装包下载地址与安装过程详细介绍(Windows版)...
  4. java优化 pdf_Java性能优化权威指南 PDF扫描[132MB]
  5. VIM文本编辑器相关命令实验总结
  6. linux 用命令安装软件,Linux安装软件的三种常用命令
  7. RealVIZ Stitcher Unlimited v5.5.1 1CD
  8. Perl 语言入门学习
  9. 局域网远程控制工具之VNC
  10. 低代码,想说爱你不容易