流畅的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 ...
随机推荐
- 下载安装go插件包报错fatal: unable to access 'https://github.com/golang/tools.git/': OpenSSL SSL_read: SSL_ERROR_SYSCALL, errno 10054
使用git命令来给vscode安装go插件的时候报错,如下: $ git clone https://github.com/golang/tools.git tools Cloning into 't ...
- eclipse out of memory
eclipse 安装目录 修改 eclipse.ini 在eclipse根目录下打开eclipse.ini,默认内容为(这里设置的是运行当前开发工具的JVM内存分配): -vmargs-Xms40m- ...
- memcached安装+绑定访问ip
安装: 1.由于memcached是基于libevent的,需要安装libevent,libevent-devel $yum -y install libevent libevent-devel 2. ...
- 使用dpkg命令卸载已经安装的软件包
如何在Ubuntu中使用dpkg命令卸载软件 http://jingyan.baidu.com/article/f54ae2fc2724a71e92b849c4.html sudo dpkg -i x ...
- 将xml文件转为c#对像
读取xml文件数据,通过序列化反序列化转为List<T>对象后,对对象进行操作.
- 在浏览器中输入url地址 -> 显示主页的过程
-来自<图解HTTP> 最近在进行前端面试方面的一些准备,看了网上许多相关的文章,发现有一个问题始终绕不开: 在浏览器中输入URL到整个页面显示在用户面前时这个过程中到底发生了什么.仔细思 ...
- Flask实战第62天:帖子详情页布局
在templates/front/下创建详情页面front_pdetail.html 编辑front.views.py创建详情页的视图函数 from flask import abort ... @b ...
- 【ModelMap】jsp中显示springmvc modelmap传递的对象
最近在做一个小网站,功能非常基础,决定用springmvc搭建. 遇到一个问题,在controller向前端传值时,比如使用ModelMap传了一个字符串,modelmap.addattribute( ...
- 分布式框架Dubbo配置和实例
准备工作: 1.ZooKeeper:需要去Apache Zookeeper官网下载Zookeeper.tar.gz包,Dubbo是依赖于Zookeeper的 2.Maven:需要去Apache Mav ...
- 【BZOJ 4513】【SDOI 2016】储能表
http://www.lydsy.com/JudgeOnline/problem.php?id=4513 设\(f(i,0/1,0/1,0/1)\)和\(g(i,0/1,0/1,0/1)\)分别表示d ...