python并发之concurrent快速入门

article/2025/8/23 16:21:52

导读:我很笨,但是我很快——计算机之所以计算能力如此出众,不在于其有多智能,而是因为它超快的执行速度,而多核心则可以进一步成倍的提高效率。在python中,concurrent库就是用于完成并发的模块之一。

01 初识concurrent

concurrent库是python内置模块之一,基于threading和multiprocessing两个模块实现,并对二者进行了很好的封装和集成,使其拥有更加简洁易用的接口函数,无需再考虑start()、join()、lock()等问题。

打开concurrent模块(默认安装位于..\Python\Python37\Lib),发现当前其仅内置了一个futures子模块,而futures子模块中,则有3个重要的.py文件,其中_base.py是最主要的模块,提供了大部分并发功能,但属于私有模块,不能被其他程序直接import,另外两个则是process和thread模块,即多进程和多线程,二者均调用_base实现主要并发接口函数。

concurrent英文原义为"并发的",futures英文原义为"未来",模块取名concurrent很好理解(java中有同名包),而子模块取名futures则用以表示未来有待完成的任务,似乎也正体现了多线程/多进程中任务队列的含义。

注:关于多线程和多进程的理解和区别本文不予展开,网上有很多通俗易懂的讲解可供查找学习。

02 Executor

Executor是concurrent.futures模块的抽象类,但一般不直接调用,而是为线程池和进程池提供了一个父类,即ThreadPoolExecutor和ProcessPoolExecutor均继承自Executor。

Executor虽然不直接调用,但却提供了几个非常重要的接口供其子类继承

  • submit(fn, *args, **kwarg):用于调用并发任务,其中参数fn是执行任务的函数,通过fn(*args **kwargs)的形式执行单个任务,返回Future对象

  • map(func, *iterables, timeout=None, chunksize=1):类似于python全局函数map,将可迭代对象异步并行映射给func函数,并返回一个新的可迭代结果。其中可通过timeout设置允许最大单个任务的延时,chunksize用于在多进程中设置分组规模,在多线程中无意义

  • shutdown(wait=True),用于在任务完成后释放所调用的资源,其中wait参数默认为True,表示当前任务执行完毕且释放已分配资源后才返回,wait设置为False时则执行shutdown后立即返回,实际不怎么应用的到。后文将会提到,由于excutor支持上下文管理器with方法,所以可避免显式调用shutdown函数。

Executor的这几个方法中,submit()和map()也是ThreadPoolExecutor和ProcessPoolExecutor两个子类的常用方法。

另外,与Executor同在_base.py模块中定义的还有future类(调度并发任务后生成对象,用于获取单个任务信息)、wait()方法(其功能类似利用threading模块实现多线程时的join方法)等,具体不再展开。

03 ThreadPoolExecutor

ThreadPoolExecutor 是 Executor 的子类,即线程池对象类,用来异步执行调度并发任务。

  • 初始化

1def __init__(self, max_workers=None, thread_name_prefix='', initializer=None, initargs=()):
2    pass

其中max_workers是最主要和最常用的初始化参数,用于设置最大线程个数,默认为CPU个数乘以5,thread_name_prefix用于设置线程名前缀,后两个初始参数为3.7版本中增加,用于在每个任务初始化时调用一个可选对象,实际一般不用。

1if max_workers is None:
2    # Use this number because ThreadPoolExecutor is often
3    # used to overlap I/O instead of CPU work.
4    max_workers = (os.cpu_count() or 1) * 5
  • 执行多线程任务

执行多线程任务有两种方式,都是继承自父类Executor中的方法,分别是submit()和map()

1from concurrent.futures import ThreadPoolExecutor as executor
2futures = [executor.submit(fun, arg) for arg in args]#方式1
3results = executor.map(fun, args)#方式2
  • 获取多线程调用结果

在使用submit执行多线程任务时,每个线程任务返回一个future对象,future对象是一个用于接收单个任务执行结果的对象,其result()方法常用于获取单任务执行结果,例如

1futures = [executor.submit(fun, arg) for arg in args]#方式1
2results = [future.result() for future in futures]

而在map执行方式中,则是直接返回单个任务执行结果的迭代器。

  • submit与map对比:二者均可用于执行线程池任务并返回结果,区别是后者直接返回执行结果;而前者返回一个future对象,在future对象中,除了可用其result()方法获得执行结果外,还有详细的方法来获取和设置任务状态,如

    • cancel():尝试取消调用

    • cancelled():如果调用被成功取消返回True

    • running():如果当前正在被执行不能被取消返回True

    • done():如果调用被成功取消或者完成running返回True

04 ThreadPoolExecutor

与ThreadPoolExecutor类似,ProcessPoolExecutor进程池也是继承自Executor类的一个子类,且很多调用接口和执行方式与前者几乎一致。

ProcessPoolExecutor官方文档内置配图

  • 初始化

1def __init__(self, max_workers=None, mp_context=None, initializer=None, initargs=()):
2    pass
3    if max_workers is None:
4        self._max_workers = os.cpu_count() or 1

这里,最大进程数默认为CPU核心个数。第二个参数与线程池类不同,是用于初始化一个多进程环境,默认调用multiporcessing模块的get_context方法。

  • 执行多进程任务:用submit或map方法,具体与多线程调用方式一致

  • 获取执行结果:与多线程获取结果方式一致

05 并发实战对比

对python多线程和多进程并发任务有所了解的都知道,对于IO密集型任务(如涉及磁盘读写较多的任务、网络响应和传输较多的下载任务等),多线程和多进程都能带来较高的并发效率,但是对于计算密集型(CPU密集型)任务(涉及的任务主要是依赖CPU计算),则多线程一般不会带来效率上的提升,甚至与串行几乎一致。

下面通过两个实例验证这一结论,并测试并发效率

  • IO密集型

我们以python爬虫请求10次网页为例,分别测试串行、多线程和多进程3种方式的执行时间

 1from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor2import math3from time import time4import requests56URLS = ['https://www.baidu.com/']*1078def get_baidu(url):9    return requests.get(url).text
10
11def multi_thread():
12    with ThreadPoolExecutor() as executor:
13        return list(executor.map(get_baidu, URLS))
14
15def multi_process():
16    with ProcessPoolExecutor() as executor:
17        return list(executor.map(get_baidu, URLS))
18
19def single():
20    return list(map(get_baidu, URLS))
21
22if __name__ == '__main__':
23    start = time()
24    single()
25    print("time used by single computing :", time()-start)
26    start = time()
27    multi_thread()
28    print("time used by multi_thread computing :", time()-start)
29    start = time()
30    multi_process()
31    print("time used by multi_process computing :", time()-start)
32"""
33time used by single computing : 7.0965657234191895
34time used by multi_thread computing : 0.41477227210998535
35time used by multi_process computing : 1.7192769050598145
36"""
  • 计算密集型

这里,我们选用官方demo,即判断一个数是否是质数的案例。

 1from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor2import math3from time import time45PRIMES = [6    112272535095293,7    112582705942171,8    112272535095293,9    115280095190773,
10    115797848077099,
11    1099726899285419]
12
13def is_prime(n):
14    pass#具体可参照官网或后台回复concurrent下载
15
16def multi_thread():
17    with ThreadPoolExecutor() as executor:
18        return list(executor.map(is_prime, PRIMES))
19
20def multi_process():
21    with ProcessPoolExecutor() as executor:
22        return list(executor.map(is_prime, PRIMES))
23
24def single():
25    return list(map(is_prime, PRIMES))
26
27if __name__ == '__main__':
28    start = time()
29    single()
30    print("time used by single computing :", time()-start)
31    start = time()
32    multi_thread()
33    print("time used by multi_thread computing :", time()-start)
34    start = time()
35    multi_process()
36    print("time used by multi_process computing :", time()-start)
37"""
38time used by single computing : 3.2942192554473877
39time used by multi_thread computing : 3.2454559803009033
40time used by multi_process computing : 2.2647616863250732
41"""

注:以上两个详细源码可在 公众号:小数志 后台回复"concurrent"下载

串行调度计算密集型任务,CPU负载曲线(33%左右)

多线程调度计算密集型任务,CPU负载曲线(33%左右)

多进程调度计算密集型任务,CPU负载曲线(100%左右)

结论:

  • 对于IO密集型任务,多线程可发挥巨大威力,甚至执行效率超过了多进程执行方式(案例中多线程效率超过多进程的原因有二:一是进程间切换相较于线程切换带来更大开销和时延,二是默认初始化参数中多线程数量是CPU核心数的5倍,而多进程数量等于CPU核心数)

  • 对于计算密集型任务,多线程由于仅调用单个CPU进行计算,所以效率与串行几乎一致,而多进程由于可以调用多个CPU的计算能力,效率要更高一些。但由于进程间切换需要开销,故其与串行效率的比值达不到核心个数(经测试,数据量足够大时,效率比值接近CPU核心数)。

06 总结

concurrent模块主要类和方法关系图

  • python自带concurrent模块实现了对多线程threading模块和多进程multiprocessing模块的高度封装和集成,使用极为方便

  • ThreadPoolExecutor类和ProcessPoolExecutor类均继承自Executor父类,二者初始化方式略有区别,但调度并发任务和获取执行结果方式几乎一致

  • 2种调度并发任务的方式:submit()和map()

  • submit()相比map而言,具有更丰富的任务定制方法

  • IO密集型任务多线程和多进程均能带来较高执行效率,而计算密集型任务则仅多进程能带来实际提升

相关阅读:

1. 多种爬虫方式对比

2. pyquery5行代码爬取百度热点

3. 5分钟速览python正则表达式

4. 我找到了二分法"作弊"的接口

5. Python的内置容器不止有list/dict

6. 地图可视化不只是pyecharts.map

7. 生成词云的几种方式


http://chatgpt.dhexx.cn/article/MlTcHegT.shtml

相关文章

Java 并发工具包(concurrent)详解

目录 一、concurrent并发包 二、ReentrantLock(可重入锁) 1、锁状态中断与可重入 2、尝试非阻塞地获取锁 3、等待可中断 4、设置公平锁 三、CountDownLatch(门栓) 四、cyclicBarrier(栅栏) 五、…

JAVA中split函数的用法

JAVA中split函数的用法 只写经常使用的,并不完整。 1.基本用法,将字符串按照指定字符串进行分割,例如: public class Main {public static void main(String[] args) {String ss "abcabcdefg";String[] split ss.sp…

C语言实现split函数

实现类似JAVA编程语言中split函数: (这里以空格为分隔符进行演示) 函数的声明:void split(char *src,const char *separator,char **dest,int *num) {}变量: 1.*src:要进行分割的字符串地址, 2…

mysql实现自定义split函数

1、自定义split函数脚本 CREATE DEFINER root% FUNCTION tjdemo.fun_get_split_string_total(f_string varchar(1000),f_delimiter varchar(5)) RETURNS int(11) LANGUAGE SQL NOT DETERMINISTIC CONTAINS SQL SQL SECURITY DEFINER COMMENT BEGIN declare returnInt int(11…

Oracle实现split函数

创建TYPE CREATE OR REPLACE TYPE TYPE_SPLIT AS TABLE OF VARCHAR2 (4000);创建函数 CREATE OR REPLACE FUNCTION SPLIT(P_STRING VARCHAR2, P_SEP VARCHAR2 : ,)RETURN TYPE_SPLITPIPELINED ISIDX PLS_INTEGER;V_STRING VARCHAR2(4000) : P_STRING; BEGINLOOPIDX : INSTR(…

java split函数的用法_java中split函数用法以及注意事项

java中split函数用法以及注意事项 发布时间:2020-04-23 10:28:23 来源:亿速云 阅读:215 作者:小新 本篇文章和大家了解一下java中split函数用法以及注意事项。有一定的参考价值,有需要的朋友可以参考一下,希…

mysql 创建函数 split_在mysql中实现split函数的几种方法

在mysql中实现split函数的几种方法 关注:98 答案:2 mip版 解决时间 2021-02-07 11:27 提问者夜落花台 2021-02-07 02:11 在mysql中实现split函数的几种方法 最佳答案 二级知识专家蓝莓九栀 2021-02-07 03:28 mysql 5.* 的版本现在没有split 函数,以下是几个自定义的split函数…

Oracle split函数

一、创建split函数 1、创建TYPE CREATE OR REPLACE TYPE TYPE_SPLIT AS TABLE OF VARCHAR2 (4000); / 2、创建split函数 CREATE OR REPLACE FUNCTION SPLIT(P_STRING VARCHAR2, P_SEP VARCHAR2 : ,)RETURN TYPE_SPLITPIPELINED ISIDX PLS_INTEGER;V_STRING VARCHAR2(4000)…

mysql有split函数么_mysql中split函数

在mysql中并没有split函数,需要自己写: 1)获得按指定字符分割的字符串的个数: Sql代码 DELIMITER$$ DROP FUNCTION IFEXISTS`sims`.`func_get_split_string_total`$$ CREATE DEFINER=`root`@`localhost` FUNCTION `func_get_split_string_total`( f_strin 在mysql中并没有sp…

Python之split函数的详解

目录 一、split函数的官方定义 二、split函数的深刻理解 二、split函数的深刻理解 split函数主要应用场景是Python对字符串的处理中(数据分析,数据处理),以及计算机二级考试的常考基础知识点。 一、split函数的官方定义 定义…

Python基础之split()函数

一、split()函数描述 split() 通过指定分隔符对字符串进行切片,如果参数 num 有指定值,则分隔 num1 个子字符串split() 方法语法: 二、split()用法 语法: str.split(str"", numstring.count(str)) 参数: str…

分割字符串split函数的正确用法(切片)

分割字符串split函数的正确用法(切片) split函数是将字符串分割为列表 函数原型: str.split(sep,maxsplit)参数说明: str:表示要进行分割的字符串sep:用于指定分隔符,可以包含多个字符,默认为None,即所有…

PostgreSQL 视图

详细了解视图、函数(存储过程)、触发器、别名 视图篇 一、视图定义 引言: 假设天气记录和城市为止的组合列表对我们的应用有用,但我 们又不想每次需要使用它时都敲入整个查询。我们可以在该查询上创建一个视图,这会给该 查询一…

SQL Server 数据库之视图(二)

视图(二) 1. 查询视图信息1.1 查询和视图设计工具1.2 关系图视图1.3 条件窗格1.4 SQL 窗格1.5 结果窗格1.6 SQL 编辑器1.7 获取有关视图的信息 2. 创建基于视图的视图3. 删除视图 1. 查询视图信息 1.1 查询和视图设计工具 打开视图的定义、显示查询视图…

hive视图

Hive的视图 应用场景将特定的列提供给用户,保护数据隐私 用于查询语句复杂的场景 通过隐藏子查询、连接和函数来简化查询的逻辑结构只保存定义,不存储数据 如果删除或更改基础表,则查询视图将失败 视图是只读的,不能插入或装载数据…

mysql 查询视图_MySQL查看视图

查看视图 是指查看数据库中,已经存在的视图的定义 查看视图,必须要有SHOW VIEW的权限 查看视图有三种方式 DESCRIBE语句 使用DESCRIBE语句,查看视图 MySQL中,使用DESCRIBE可以查看视图的字段信息,其中,包括…

UML 视图

概述 事物(Things)、关系(Relationships) 是组成 UML 模型的基本模型元素,图由模型元素事物和关系构成,视图由各种图构成。 随着系统复杂性的增加,建模就成了必不可少的工作。理想情况下,系统由单一的图形…

MYSQL基础之 视图

概念 什么是视图? 视图是基于 SQL 语句的结果集的可视化的表。 当然视图也是数据库中对象之一,其它的对象包括:表,数据字典,约束,索引,触发器,存储过程,以及存储函数。这些后面再…

ClickHouse 物化视图

像通常的计算一样,强大的能力意味着至少有一点复杂性。这篇由两部分组成的文章通过准确解释物化视图的工作原理来填补空白,以便即使是初学者也可以有效地使用它们。我们将提供几个详细的示例,您可以根据自己的用途进行调整。在此过程中&#…

mysql通过视图插入数据_数据库视图 sql

数据库必知词汇:数据库视图 视图是从一个或几个基本表(或视图)中导出的虚拟的表。在系统的数据字典中仅存放了视图的定义,不存放视图对应的数据。视图与表(有时为与视图区别,也称表为基本表——Base Table)不同,视图所对应的数据不…