流畅的python第十七章使用期物处理并发
从 Python 3.4 起,标准库中有两个名为 Future 的类:concurrent.futures.Future 和
asyncio.Future。这两个类的作用相同:两个 Future 类的实例都表示可能已经完成或
者尚未完成的延迟计算
我们要记住一件事:通常情况下自己不应该创建期物,而只能由并发框架
(concurrent.futures 或 asyncio)实例化。原因很简单:期物表示终将发生的事
情,而确定某件事会发生的唯一方式是执行的时间已经排定。因此,只有排定把某件事交
给 concurrent.futures.Executor 子类处理时,才会创建
concurrent.futures.Future 实例。例如,Executor.submit() 方法的参数是一个可
调用的对象,调用这个方法后会为传入的可调用对象排期,并返回一个期物。
客户端代码不应该改变期物的状态,并发框架在期物表示的延迟计算结束后会改变期物的
状态,而我们无法控制计算何时结束。
这两种期物都有 .done() 方法,这个方法不阻塞,返回值是布尔值,指明期物链接的可
调用对象是否已经执行。客户端代码通常不会询问期物是否运行结束,而是会等待通知。
因此,两个 Future 类都有 .add_done_callback() 方法:这个方法只有一个参数,类
型是可调用的对象,期物运行结束后会调用指定的可调用对象。
此外,还有 .result() 方法。在期物运行结束后调用的话,这个方法在两个 Future 类
中的作用相同:返回可调用对象的结果,或者重新抛出执行可调用的对象时抛出的异常。
可是,如果期物没有运行结束,result 方法在两个 Future 类中的行为相差很大。对
concurrency.futures.Future 实例来说,调用 f.result() 方法会阻塞调用方所在的
线程,直到有结果可返回。此时,result 方法可以接收可选的 timeout 参数,如果在指
定的时间内期物没有运行完毕,会抛出 TimeoutError 异常。读到 18.1.1 节你会发
现,asyncio.Future.result 方法不支持设定超时时间,在那个库中获取期物的结果最
好使用 yield from 结构。不过,对 concurrency.futures.Future 实例不能这么做。
这两个库中有几个函数会返回期物,其他函数则使用期物,以用户易于理解的方式实现自
身。使用 17-3 中的 Executor.map 方法属于后者:返回值是一个迭代器,迭代器的
__next__ 方法调用各个期物的 result 方法,因此我们得到的是各个期物的结果,而非
期物本身。
阻塞型I/O和GIL
CPython 解释器本身就不是线程安全的,因此有全局解释器锁(GIL),一次只允许使用
一个线程执行 Python 字节码。因此,一个 Python 进程通常不能同时使用多个 CPU 核
心
Python 标准库中的所有阻塞型 I/O 函数都会释放 GIL,允许其他线程运
行。time.sleep() 函数也会释放 GIL。因此,尽管有 GIL,Python 线程还是能在 I/O
密集型应用中发挥作用。
如何在 CPU 密集型作业中使用 concurrent.futures 模块轻松绕开 GIL
concurrent.futures 模块的文档
(https://docs.python.org/3/library/concurrent.futures.html)副标题是“Launching parallel
tasks”(执行并行任务)。这个模块实现的是真正的并行计算,因为它使用
ProcessPoolExecutor 类把工作分配给多个 Python 进程处理。因此,如果需要做 CPU
密集型处理,使用这个模块能绕开 GIL,利用所有可用的 CPU 核心。
ThreadPoolExecutor.__init__ 方法需要 max_workers 参数,指定线程池中线程
的数量。在 ProcessPoolExecutor 类中,那个参数是可选的,而且大多数情况下不使用
——默认值是 os.cpu_count() 函数返回的 CPU 数量。这样处理说得通,因为对 CPU 密
集型的处理来说,不可能要求使用超过 CPU 数量的职程。而对 I/O 密集型处理来说,可
以在一个 ThreadPoolExecutor 实例中使用 10 个、100 个或 1000 个线程;最佳线程数
取决于做的是什么事,以及可用内存有多少,因此要仔细测试才能找到最佳的线程数。
from time import sleep, strftime
from concurrent import futures def display(*args):
print(strftime('[%H:%M:%S]'), end=' ')
print(*args) def loiter(n):
msg = '{}loiter{}: doing nothing for {}s...'
display(msg.format('\t'*n, n, n))
sleep(n)
msg = '{}loiter({}): done.'
display(msg.format('\t'*n, n))
return n * 10 def main():
display('Script starting.')
executor = futures.ThreadPoolExecutor(max_workers=3)
results = executor.map(loiter, range(5))
display('result:', results)
display('Waiting for individual results:')
for i, result in enumerate(results):
display('result {}: {}'.format(i, result)) main()
Executor.map 函数易于使用,不过有个特性可能有用,也可能没用,具体情况取决于需
求:这个函数返回结果的顺序与调用开始的顺序一致。如果第一个调用生成结果用时 10
秒,而其他调用只用 1 秒,代码会阻塞 10 秒,获取 map 方法返回的生成器产出的第一个
结果。在此之后,获取后续结果时不会阻塞,因为后续的调用已经结束。如果必须等到获
取所有结果后再处理,这种行为没问题;不过,通常更可取的方式是,不管提交的顺序,
只要有结果就获取。为此,要把 Executor.submit 方法和 futures.as_completed 函
数结合起来使用
线程和多进程的替代方案
Python 自 0.9.8 版(1993 年)就支持线程了,concurrent.futures 只不过是使用线程的
最新方式。Python 3 废弃了原来的 thread 模块,换成了高级的 threading 模块
(https://docs.python.org/3/library/threading.html)。 如果
futures.ThreadPoolExecutor 类对某个作业来说不够灵活,可能要使用 threading 模
块中的组件(如 Thread、Lock、Semaphore 等)自行制定方案,比如说使用 queue 模
块(https://docs.python.org/3/library/queue.html)创建线程安全的队列,在线程之间传递数
据。futures.ThreadPoolExecutor 类已经封装了这些组件。
threading 模块自 Python 1.5.1(1998 年)就已存在,不过有些人仍然继续使用旧的 thread 模块。Python 3 把
本文档由Linux公社 www.linuxidc.com 整理
thread 模块重命名为 _thread,以此强调这是低层实现,不应该在应用代码中使用。
对 CPU 密集型工作来说,要启动多个进程,规避 GIL。创建多个进程最简单的方式是,
使用 futures.ProcessPoolExecutor 类。不过和前面一样,如果使用场景较复杂,需
要更高级的工具。multiprocessing 模块
(https://docs.python.org/3/library/multiprocessing.html)的 API 与 threading 模块相仿,不
过作业交给多个进程处理。对简单的程序来说,可以用 multiprocessing 模块代替
threading 模块,少量改动即可。不过,multiprocessing 模块还能解决协作进程遇到
的最大挑战:在进程之间传递数据。
总结一下,python中有GIL锁,导致无法和正常使用线程,但是对于IO密集型作业,由于python标准库中的所有堵塞型I/O函数都会释放GIL,允许其他线程运行,所以不妨碍多线程的使用。而对于CPU密集型作业,可以使用concurrent.futures模块绕开GIL。
流畅的python第十七章使用期物处理并发的更多相关文章
- 流畅的python第七章函数装饰器和闭包学习记录
本章讨论的话题 python如何计算装饰器句法 python如何判断变量是不是局部的(通过函数内部是否给变量赋值过来判断是否是局部变量) 闭包存在的原因和工作原理(闭包是一种函数,它会保留定义函数时存 ...
- 流畅的python第四章文本和字节序列学习记录
字符问题 把码位转化成字节序列的过程是编码,把字节序列转化成码位的过程是解码 把unicode字符串当成人类可读的文本,码位当成机器可读的, 将字节序列编程人类可读是解码,把字符串编码成字节序列是编码 ...
- 流畅的python第十一章接口学习记录
鸭子协议(忽略对象真正类型,转而关注对象有没有实现所需的方法,签名和语义) 标准库中的抽象基类 collections.abc模块中的抽象基类 抽象方法是抽象基类中用来强制子类必须实现的方法,如果子类 ...
- 流畅的python第五章一等函数学习记录
在python中,函数是一等对象,一等对象是满足以下条件的程序实体 1在运行时创建 2能复制给变量或数据结构的元素 3能作为参数传给函数 4能作为函数的返回结果 高阶函数(接受函数作为参数或者把函数作 ...
- Python 使用期物处理并发
抨击线程的往往是系统程序员,他们考虑的使用场景对一般的应用程序员来说,也许一生都不会遇到--应用程序员遇到的使用场景,99% 的情况下只需知道如何派生一堆独立的线程,然后用队列收集结果. 示例:网络下 ...
- 流畅的python第十三章正确重载运算符
运算符重载基础 不能重载内置类型的运算符 不能新建运算符,只能重载现有的 某些运算符不能重载-------is,and,or和not(不过位运算符&,|和~可以) 一元运算符
- 流畅的python第三章字典和集合学习记录
什么是可散列的数据类型 如果一个对象是可散列的,那么在这个对象的生命周期中,他的散列值是不变的,而且这个对象需要实现__hash__()方法.另外可散列对象还要有__qe__()方法.这样才能跟其他键 ...
- python经典书籍必看:流畅的Python
作者:熊猫烧香 链接:www.pythonheidong.com/blog/article/26/ 来源:python黑洞网 目标读者 本书的目标读者是那些正在使用 Python,又想熟悉 Pytho ...
- 《流畅的python》读书笔记
流畅的python 第1章 python数据模型 ---1.1 一摞Python风格的纸牌 特殊方法,即__method__,又被称为魔术方法(magic method)或者双下方法(dunder-m ...
随机推荐
- stl 学习笔记
1. Erasing multiple objects from a std::vector https://stackoverflow.com/questions/3487717/erasing-m ...
- Linux下安装PHP环境(非集成)
一.安装Apache1.到官网下载 http://httpd.apache.org/download.cgi 2.安装apache [root@localhost 52lamp]# mkdir ...
- Android与html5交互 -- WebView使用(一)
Android中使用WebView可加载html5,具体步骤如下: (前提:本地Html5存放到assets文件夹下) 一:使用WebView加载Html5,简单显示 1:清单文件中添加访问权限:an ...
- Notepad++7.4.2的配置使用详情
之前有写过一篇notepad的使用说明,没想到稀里糊涂更新了几次之后,我以前的配置什么的全干掉了,而且我打开新版后,发现和以前还有点不一样了.那就继续这个高级版本再来一边吧. 1.Zen Coding ...
- hdu 4135 Co-prime (素数打表+容斥原理)
题目链接 题意:问从A到B中与N互素的个数. 题解: 利用容斥原理:先求出与n互为素数的个数. 可以先将 n 进行素因子分解,然后用区间 x 除以 素因子,就得到了与 n 的 约数是那个素因子的个数, ...
- android ListView 在初始化时多次调用getView()原因分析
今天在做一个功能:在初始化ListView时,把第一行背景置为黄色,同时保存第一行对象,用于在点击其他行时将该行重新置为白色. if(position==0){ convertView.setBack ...
- 2010-2011 ACM-ICPC, NEERC, Southern Subregional Contest C Explode 'Em All
暴力枚举,状态压缩. 枚举哪几行放,复杂度为$O(2^{25})$,大概有$3000$多万种情况.假设有$x$行放了,没放的那几行状态或起来为$st$,如果$st$中$1$的个数大于$x$,那么不可取 ...
- Windows命令远程执行工具Winexe
Windows命令远程执行工具Winexe 在对Windows系统执行渗透测试中,通过各种方式可以获取目标主机的用户名和密码.这时,只要对方主机开启文件共享服务,就可以借助Winexe工具远程执行 ...
- [NOI2016]循环之美(杜教筛)
首先要求每个数互不相等,故有$x\perp y$. 可以发现$\frac{x}{y}$在$k$进制下为纯循环小数的充要条件为$x\cdot k^{len}\equiv x(mod\ y)$,即$y\p ...
- JZYZOJ1445 [noip2014day1-T3]飞扬的小鸟 动态规划 完全背包
http://172.20.6.3/Problem_Show.asp?id=1445 很容易看出来动态规划的本质,但是之前写的时候被卡了一下(不止一下),还是写一下题解. 直接暴力O(n*m^2)大概 ...