流畅的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 ...
随机推荐
- MapReduce框架组成
原文地址:MapReduce的架构组成 MapReduce基本架构 分而治之,并行计算 一句话 -- 整体主从架构,map加reduce:map.split入磁盘,数据对分partition:shuf ...
- Eclipse中部署ES源码运行
https://stackoverflow.com/questions/40924671/how-to-build-elasticsearch-source-code-using-gradle Gra ...
- 用ab进行POST,GET压力测试,且定义header及json的内容
这个以前没作过,找了文档.搞定. 要求时间的单curl测试的方式如下: time curl http://xxxxx/hq/restapi/isStart curl用来用post请求,且有header ...
- 打印之Lodop
前序 前面遇到一个问题:在线打印合同.通过各方查找资料和请教他人,终于完美的解决了这个问题.其中的解决方案,可以查看:http://www.cnblogs.com/zcy-xy/p/4290436.h ...
- HRBUST 1311 火影忍者之~忍者村
求连通块. $ABC$之间建好边,然后计算连通块的个数. #pragma comment(linker, "/STACK:1024000000,1024000000") #incl ...
- python之面向对象编程一
概述: 面向过程:根据业务逻辑从上到下写垒代码 函数式:将某功能代码封装到函数中,日后便无需重复编写,仅调用函数即可 面向对象:对函数进行分类和封装,让开发“更快更好更强...” 面向过程编程最易被初 ...
- 进入CentOS7紧急模式恢复root密码
第一步.重启CentOS7,在以下界面选择要编辑的内核(一般第一个),按e进入编辑界面 第二步.在编辑界面找到如下一行,将ro改为rw init=/sysroot/bin/sh.改完后<Ctrl ...
- putty对Linux上传下载文件或文件夹
putty是一个开源软件,目前为止最新版本为0.70.对于文件或文件夹的上传下载,在Windows下它提供了pscp和psftp两个命令. (1).pscp pscp在命令提示符中使用,只要putty ...
- 离线情报分析工具CaseFile
离线情报分析工具CaseFile CaseFile是Maltego的姊妹工具,功能非常类似于Maltego.CaseFile主要针对数据进行离线分析,缺少Maltego的数据采集功能.它可以导入各 ...
- Sd - Hibernate
Sd - Hibernate 附:http://blog.csdn.net/ta8210/article/details/1582162 数据库连接池实现原理 http://hi.baidu.com/ ...