从 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第十七章使用期物处理并发的更多相关文章

  1. 流畅的python第七章函数装饰器和闭包学习记录

    本章讨论的话题 python如何计算装饰器句法 python如何判断变量是不是局部的(通过函数内部是否给变量赋值过来判断是否是局部变量) 闭包存在的原因和工作原理(闭包是一种函数,它会保留定义函数时存 ...

  2. 流畅的python第四章文本和字节序列学习记录

    字符问题 把码位转化成字节序列的过程是编码,把字节序列转化成码位的过程是解码 把unicode字符串当成人类可读的文本,码位当成机器可读的, 将字节序列编程人类可读是解码,把字符串编码成字节序列是编码 ...

  3. 流畅的python第十一章接口学习记录

    鸭子协议(忽略对象真正类型,转而关注对象有没有实现所需的方法,签名和语义) 标准库中的抽象基类 collections.abc模块中的抽象基类 抽象方法是抽象基类中用来强制子类必须实现的方法,如果子类 ...

  4. 流畅的python第五章一等函数学习记录

    在python中,函数是一等对象,一等对象是满足以下条件的程序实体 1在运行时创建 2能复制给变量或数据结构的元素 3能作为参数传给函数 4能作为函数的返回结果 高阶函数(接受函数作为参数或者把函数作 ...

  5. Python 使用期物处理并发

    抨击线程的往往是系统程序员,他们考虑的使用场景对一般的应用程序员来说,也许一生都不会遇到--应用程序员遇到的使用场景,99% 的情况下只需知道如何派生一堆独立的线程,然后用队列收集结果. 示例:网络下 ...

  6. 流畅的python第十三章正确重载运算符

    运算符重载基础 不能重载内置类型的运算符 不能新建运算符,只能重载现有的 某些运算符不能重载-------is,and,or和not(不过位运算符&,|和~可以) 一元运算符

  7. 流畅的python第三章字典和集合学习记录

    什么是可散列的数据类型 如果一个对象是可散列的,那么在这个对象的生命周期中,他的散列值是不变的,而且这个对象需要实现__hash__()方法.另外可散列对象还要有__qe__()方法.这样才能跟其他键 ...

  8. python经典书籍必看:流畅的Python

    作者:熊猫烧香 链接:www.pythonheidong.com/blog/article/26/ 来源:python黑洞网 目标读者 本书的目标读者是那些正在使用 Python,又想熟悉 Pytho ...

  9. 《流畅的python》读书笔记

    流畅的python 第1章 python数据模型 ---1.1 一摞Python风格的纸牌 特殊方法,即__method__,又被称为魔术方法(magic method)或者双下方法(dunder-m ...

随机推荐

  1. Django 如何实现文件下载

    1. 思路: 文件,让用户下载 - a标签+静态文件 - 设置响应头(django如何实现文件下载) 2. a标签实现 <a href="/static/xxx.xlsx"& ...

  2. LeetCode解题报告—— Container With Most Water & 3Sum Closest & Letter Combinations of a Phone Number

    1.  Container With Most Water Given n non-negative integers a1, a2, ..., an, where each represents a ...

  3. python读写hdf5及cdf格式文件

    Python write and read hdf5 file http://stackoverflow.com/questions/20928136/input-and-output-numpy-a ...

  4. 【hdoj_1085】Holding Bin-Laden Captive![母函数]

    题目:http://acm.hdu.edu.cn/showproblem.php?pid=1085 可以这样理解题意:给出1元,2元和5元的三种硬币若干,每种硬币数量给出,现在可以从所有的硬币中,选出 ...

  5. 如何把Android Studio项目转换成Eclipse的项目

    1. 找到这个目录:项目名称\app\src\main,并把它导入到Eclipse里 2. 把项目名称\app\libs文件夹拷贝到新生成的项目下,并删除掉.DS_Store文件 3. 这时再看还有什 ...

  6. 【剑指offer】面试题 4. 二维数组中的查找

    面试题 4. 二维数组中的查找 题目:在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序. 请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该 ...

  7. 【剑指offer】面试题 64. 求 1+2+3+...+n

    面试题 64. 求 1+2+3+...+n 题目:求1+2+3+...+n,要求不能使用乘除法.for.while.if.else.switch.case等关键字及条件判断语句(A?B:C). 1.采 ...

  8. iwebshop 自动给css js链接加版本信息

    lib/core/tag_class.php case 'theme:': $path = $matches[4]; $exts = strtolower(substr($matches[4], st ...

  9. Comet OJ CCPC-Wannafly Winter Camp Day1 (Div2, online mirror) F.爬爬爬山-最短路(Dijkstra)(两个板子)+思维(mdzz...) zhixincode

    爬爬爬山 已经提交 已经通过 9.83% Total Submission:417 Total Accepted:41 题目描述 爬山是wlswls最喜欢的活动之一. 在一个神奇的世界里,一共有nn座 ...

  10. UTF-8 与 BIG-5 转码

    BIG-5 轉 UTF-8 若要將一個文字檔從 BIG-5 編碼轉換為 UTF-8 編碼,可以執行: iconv -f BIG-5 -t UTF-8 big5.txt > utf8.txt 其中 ...