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

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

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

此外,使用线程池可以有效地控制系统中并发线程的数量。当系统中包含有大量的并发线程时,会导致系统性能急剧下降,甚至导致 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) 的结果。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/370704.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Google Guava缓存

这篇文章是我在Google Guava上系列文章的续篇,这次涵盖了Guava Cache。 与HashMap或ConcurrentHashMap相比,Guava Cache提供了更大的灵活性和功能,但不像使用EHCache或Memcached那样繁重(就此而言,它很健壮&#xff0c…

html 三列布局(两列自适应,一列固定宽度)

不做过多解释:主要是记录一个完整的布局样式,实现页面大致三列其中左右两列是自适应宽度,中间固定宽度效果。 不多少代码奉上: CSS样式代码: /*********************公共标签样式********************//************…

jsp常用动作

jsp:include 动态包含; jsp:forward 转发; jsp:useBean 实例化bean对象; jsp:setProperty 设置一个属性值 jsp:getProperty 获取一个属性值 jsp:param 动态传参数; jsp:plugin 生成一个插件 jsp:useBean 实例化一个对象…

单曲循环 翻译_歌单 | 单曲循环amp;热评

December2020/12/ 写在前面的话 /本来打算在跨年的时候才更文,但是吧又觉得空出这最后一个月有点苍白,然后最近一直夜半网抑云(敏感ing)就想到可以做一期分享歌单的推文,分享一些最近听得频繁的歌曲(还不是刷抖音刷出来的)。《暧昧》// 王菲徘…

python的字符串内建函数

python的字符串内建函数 字符串方法是从python1.6到2.0慢慢加进来的——它们也被加到了Jython中。 这些方法实现了string模块的大部分方法,如下表所示列出了目前字符串内建支持的方法,所有的方法都包含了对Unicode的支持,有一些甚至是专门用…

休息使用Jersey –包含JAXB,异常处理和客户端程序的完整教程

最近,我开始使用Jersey API开发一个Restful Web服务项目。 在线提供了一些教程,但是我遇到了异常处理方面的一些问题,而且在使用JaxB和提供异常处理方法的完整项目中找不到任何地方。 因此,一旦我能够使用带有异常处理和客户端程序…

python基于web可视化_独家 | 基于Python实现交互式数据可视化的工具(用于Web)

转自:数据派ID:datapi 作者:Alark Joshi 翻译:陈雨琳 校对:吴金笛 本文2200字,建议阅读8分钟。 本文将介绍实现数据可视化的软件包。 这学期(2018学年春季学期)我教授了一门关于数据…

SASS简介及使用方法

一、什么是Sass Sass (Syntactically Awesome StyleSheets)是css的一个扩展开发工具,它允许你使用变量、条件语句等,使开发更简单可维护。这里是官方文档。 二、基本语法 1)变量 sass的变量名必须是一个$符号开头,后面紧跟变量名…

【转】Java方向如何准备BAT技术面试答案(汇总版)

原文地址:http://www.jianshu.com/p/1f1d3193d9e3 这个主题的内容之前分三个篇幅分享过,导致网络上传播的比较分散,所以本篇做了一个汇总,同时对部分内容及答案做了修改,欢迎朋友们吐槽、转发。因为篇幅长度和时间的原…

numpy维度交换_“lazy”的transpose()函数——从numpy 数组的内存布局讲起

1 数组的两种内存布局方式行优先与列优先首先我们回顾一下,矩阵数据在内存中的两种布局方式:行优先(row-major):以行为优先单位,在内存中逐行存储/读取;对于多维,意味着当线性扫描内…

云耀服务器切换系统,【计算】云耀服务器-常见操作汇总指南

通过上期的介绍,相信大家对于云耀云服务器的基本知识有了一个了解。云耀云服务器是一个具备独立、完整的操作系统和网络功能,可快速搭建简单应用的新一代云服务器。接下来,本期为大家带来关于云耀云服务器使用中的一些简单方法和小技巧。1.云…

机器学习应该准备哪些数学预备知识?

转 https://www.zhihu.com/question/36324957 https://www.zhihu.com/question/36324957/answer/139408269 机器学习应该准备哪些数学预备知识? 数据分析师,工作中经常使用机器学习模型,但是以调库为主。 自己一直也在研究算法,也…

react usecontext_Vue3原理实战运用,我用40行代码把他装进了React做状态管理

前言vue-next是Vue3的源码仓库,Vue3采用lerna做package的划分,而响应式能力vue/reactivity被划分到了单独的一个package中。如果我们想把它集成到React中,可行吗?来试一试吧。使用示例话不多说,先看看怎么用的解解馋吧…

Spring MVC –自定义RequestMappingHandlerMapping

在xml bean定义文件中使用<mvc&#xff1a;annotation-driven />配置Spring MVC时&#xff0c;在内部将一个名为RequestMappingHandlerMapping的组件注册到Spring MVC。 该组件或通常是HandlerMapping组件负责将请求URI路由到处理程序&#xff0c;这些处理程序是使用Requ…

css的三个特性 背景透明设置

关于行内元素&#xff08;补充一点&#xff09; 行内元素只能容纳文本或其他行内元素。&#xff08;a特殊a里面可以放块级元素&#xff09; 例子&#xff1a; 关于行高tip: 选择器的嵌套层级不应大于3级&#xff0c;位置靠后的限定条件应尽可能的精确。 属性定义必须另起一行…

比较容易犯的一些智障错误(不定时修改)

无论在什么学习中&#xff0c;在成长的过程中&#xff0c;注定要犯一些错误&#xff0c;有些比较高级的错误&#xff0c;有些是比较智障的错误。那么在oi的学习中&#xff0c;我们最讨厌的就是一些智障的小错误&#xff0c;因为如果是大错误的话一般情况下在测试样例的时候都是…

ccs安装多版本编译器离线_大数据分析:学习工具JDK,在线安装指南

hadoop是使用Java语言开发的并且Hadoop运行需要有Java环境的支持&#xff0c;因此在安装hadoop之前需要安装Java开发环境即JDK(Java Development Kit)。安装前首先向大家介绍以一下本文会用到的几个词&#xff1a;JAVA_HOME:一是为了方便引用&#xff0c;比如&#xff0c;JDK安…

HTML基础入门学习准备篇

在学习前端的开始&#xff0c;让我们一起来了解什么是HTML5时代的大前端开发和全栈开发的定义传统的前端&#xff1a;切图-标签和样式-实现效果H5时代的前端&#xff1a;一、需要各端的兼容开发二、可以用于APP开发和移动站点的开发三、Ajax服务器端技术开发四、高级设计模式和…

asp.net尚未在web服务器上注册_最新版Web服务器项目详解 00 项目概述

点 击 关 注 上 方&#xff02;两猿社&#xff02;设 为&#xff02;置 顶 或 星 标&#xff02;&#xff0c;干 货 第 一 时 间 送 达。互 联 网 猿 | 两 猿 社TineyWebServerLinux下C轻量级Web服务器&#xff0c;助力初学者快速实践网络编程&#xff0c;搭建属于自己的服务器…

python正则r的作用_Python正则表达式,这一篇就够了!

原标题&#xff1a;Python正则表达式&#xff0c;这一篇就够了&#xff01;大多数编程语言的正则表达式设计都师从Perl&#xff0c;所以语法基本相似&#xff0c;不同的是每种语言都有自己的函数去支持正则&#xff0c;今天我们就来学习 Python中关于 正则表达式的函数。re模块…