流畅的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 ...
随机推荐
- POJ 1177 Picture(线段树:扫描线求轮廓周长)
题目链接:http://poj.org/problem?id=1177 题目大意:若干个矩形,求这些矩形重叠形成的图形的轮廓周长. 解题思路:这里引用一下大牛的思路:kuangbin 总体思路: 1. ...
- ZOJ-3314
CAPTCHA Time Limit: 1000 MS Memory Limit: 32768 KB 64-bit integer IO format: %lld , %llu Java class ...
- 深度学习方法:受限玻尔兹曼机RBM(三)模型求解,Gibbs sampling
欢迎转载,转载请注明:本文出自Bin的专栏blog.csdn.net/xbinworld. 技术交流QQ群:433250724,欢迎对算法.技术.应用感兴趣的同学加入. 接下来重点讲一下RBM模型求解 ...
- OpenFalcon-SuitAgent
http://www.cnblogs.com/qianlong2016/archive/2016/09/08/5853899.html
- Entity Framework中使用DbCompiledModel中遇到的坑和解决方案
前段时间,在公司做项目时,引入Entity Framework Code First的方法. 我们公司的软件为SaaS结构,有N个企业注册,其中SQL Server中有一张表为t_User_企业注册号 ...
- gulp-babel,es6转es5
npm install --save-dev gulp-babel npm install --save-dev babel-preset-es2015 var gulp = require(&quo ...
- hdu多校4
Problem L. Graph Theory Homework 思路:很容易想到一步从 1 走到 n 最优. #include<bits/stdc++.h> #define LL lon ...
- Linux命令之which
which [选项] [命令] 将命令的完整路径写入标准输出.具体是在环境变量PATH设置的目录里查找符合条件的文件,而环境变量PATH中保存了查找命令时需要遍历的目录. (1).选项 -v,-V,- ...
- 2. let和const命令--ES6
1. let命令 let 命令不存在变量提升let 命令 只在let命令所在的代码块内有效. { let a = 10; var b = 1; } a // ReferenceError: a is ...
- 【UOJ #179】线性规划 单纯形模板
http://uoj.ac/problem/179 终于写出来了单纯性算法的板子,抄的网上大爷的qwq 辅助线性规划找非基变量时要加个随机化才能A,我也不知道为什么,卡精度吗? 2017-3-6UPD ...