对比一个简单的多线程程序和对应的 asyncio 版,说明多线程和异步任务之间的关

asyncio.Future 类与 concurrent.futures.Future 类之间的区别
摒弃线程或进程,如何使用异步编程管理网络应用中的高并发
在异步编程中,与回调相比,协程显著提升性能的方式
如何把阻塞的操作交给线程池处理,从而避免阻塞事件循环
使用 asyncio 编写服务器,重新审视 Web 应用对高并发的处理方式
为什么 asyncio 已经准备好对 Python 生态系统产生重大影响

线程与协程对比

import threading
import itertools
import time
import sys class Signal:
go = True def spin(msg, signal):
write, flush = sys.stdout.write, sys.stdout.flush
for char in itertools.cycle('|/-\\'):
status = char + ' ' + msg
write(status)
flush()
write('\x08' * len(status))
time.sleep(.1)
if not signal.go:
break
write(' ' * len(status) + '\x08' * len(status)) def slow_function():
time.sleep(3)
return 42 def supervisor():
signal = Signal()
spinner = threading.Thread(target=spin, args=('thinking!', signal))
print('spinner object:', spinner)
spinner.start()
result = slow_function()
signal.go = False
spinner.join()
return result def main():
result = supervisor()
print('Answer:', result) if __name__ == '__main__':
main()

以上是threading

import asyncio
import itertools
import sys @asyncio.coroutine
def spin(msg):
write, flush = sys.stdout.write, sys.stdout.flush
for char in itertools.cycle('|/-\\'):
status = char + ' ' + msg
write(status)
flush()
write('\x08' * len(status))
try:
yield from asyncio.sleep(.1)
except asyncio.CancelledError:
break
write(' ' * len(status) + '\x08' * len(status)) @asyncio.coroutine
def slow_function():
yield from asyncio.sleep(3)
return 42 @asyncio.coroutine
def supervisor():
spinner = asyncio.async(spin('thinking!'))
print('spinner object:', spinner)
result = yield from slow_function()
spinner.cancel()
return result def main():
loop = asyncio.get_event_loop()
result = loop.run_until_complete(supervisor())
loop.close()
print('Answer:', result) if __name__ == '__main__':
main()

以上是asyncio

除非想阻塞主线程,从而冻结事件循环或整个应用,否则不要在 asyncio 协
程中使用 time.sleep(...)。如果协程需要在一段时间内什么也不做,应该使用
yield from asyncio.sleep(DELAY)

使用 @asyncio.coroutine 装饰器不是强制要求,但是强烈建议这么做,因为这样能在
一众普通的函数中把协程凸显出来,也有助于调试:如果还没从中产出值,协程就被垃圾
回收了(意味着有操作未完成,因此有可能是个缺陷),那就可以发出警告。这个装饰器
不会预激协程。

线程与协程之间的比较还有最后一点要说明:如果使用线程做过重要的编程,你就知道写
出程序有多么困难,因为调度程序任何时候都能中断线程。必须记住保留锁,去保护程序
中的重要部分,防止多步操作在执行的过程中中断,防止数据处于无效状态。
而协程默认会做好全方位保护,以防止中断。我们必须显式产出才能让程序的余下部分运
行。对协程来说,无需保留锁,在多个线程之间同步操作,协程自身就会同步,因为在任
意时刻只有一个协程运行。想交出控制权时,可以使用 yield 或 yield from 把控制权
交还调度程序。这就是能够安全地取消协程的原因:按照定义,协程只能在暂停的 yield
处取消,因此可以处理 CancelledError 异常,执行清理操作。

asyncio与concurrent.future的区别

期物只是调度执行某物的结果。在 asyncio 包
中,BaseEventLoop.create_task(...) 方法接收一个协程,排定它的运行时间,然后
返回一个 asyncio.Task 实例——也是 asyncio.Future 类的实例,因为 Task 是
Future 的子类,用于包装协程。这与调用 Executor.submit(...) 方法创建
concurrent.futures.Future 实例是一个道理。
与 concurrent.futures.Future 类似,asyncio.Future 类也提供了
.done()、.add_done_callback(...) 和 .result() 等方法。前两个方法的用法与
17.1.3 节所述的一样,不过 .result() 方法差别很大。
asyncio.Future 类的 .result() 方法没有参数,因此不能指定超时时间。此外,如果
调用 .result() 方法时期物还没运行完毕,那么 .result() 方法不会阻塞去等待结果,
而是抛出 asyncio.InvalidStateError 异常。
然而,获取 asyncio.Future 对象的结果通常使用 yield from,从中产出结果,如示例
18-8 所示。
使用 yield from 处理期物,等待期物运行完毕这一步无需我们关心,而且不会阻塞事件
循环,因为在 asyncio 包中,yield from 的作用是把控制权还给事件循环。
注意,使用 yield from 处理期物与使用 add_done_callback 方法处理协程的作用一
样:延迟的操作结束后,事件循环不会触发回调对象,而是设置期物的返回值;而 yield
from 表达式则在暂停的协程中生成返回值,恢复执行协程。
总之,因为 asyncio.Future 类的目的是与 yield from 一起使用,所以通常不需要使
用以下方法。
无需调用 my_future.add_done_callback(...),因为可以直接把想在期物运行结
束后执行的操作放在协程中 yield from my_future 表达式的后面。这是协程的一
大优势:协程是可以暂停和恢复的函数。
无需调用 my_future.result(),因为 yield from 从期物中产出的值就是结果
(例如,result = yield from my_future)。
当然,有时也需要使用 .done()、.add_done_callback(...) 和 .result() 方法。但
是一般情况下,asyncio.Future 对象由 yield from 驱动,而不是靠调用这些方法驱
动。

对协程来说,获取 Task 对象有两种主要方式。
asyncio.async(coro_or_future, *, loop=None)
  这个函数统一了协程和期物:第一个参数可以是二者中的任何一个。如果是 Future
或 Task 对象,那就原封不动地返回。如果是协程,那么 async 函数会调用
loop.create_task(...) 方法创建 Task 对象。loop= 关键字参数是可选的,用于传入
事件循环;如果没有传入,那么 async 函数会通过调用 asyncio.get_event_loop() 函
数获取循环对象。
BaseEventLoop.create_task(coro)
  这个方法排定协程的执行时间,返回一个 asyncio.Task 对象。如果在自定义的
BaseEventLoop 子类上调用,返回的对象可能是外部库(如 Tornado)中与 Task 类兼容
的某个类的实例。

使用 asyncio 包时,我们编写的异步代码中包含由 asyncio 本身驱动的
协程(即委派生成器),而生成器最终把职责委托给 asyncio 包或第三方库(如
aiohttp)中的协程。这种处理方式相当于架起了管道,让 asyncio 事件循环(通过我
们编写的协程)驱动执行低层异步 I/O 操作的库函数。

import asyncio

import aiohttp

from ..chapter17.flags import BASE_URL, save_flag, show, main

@asyncio.coroutine
def get_flag(cc):
url = '{}/{cc}/{cc}.gif'.format(BASE_URL, cc=cc.lower())
resp = yield from aiohttp.request('GET', url)
image = yield from resp.read()
return image @asyncio.coroutine
def download_one(cc):
image = yield from get_flag(cc)
show(cc)
save_flag(image, cc.lower() + '.gif')
return cc def download_many(cc_list):
loop = asyncio.get_event_loop()
to_do = [download_one(cc) for cc in sorted(cc_list)]
wait_coro = asyncio.wait(to_do)
res, _ = loop.run_until_complete(wait_coro)
loop.close() return len(res) if __name__ == '__main__':
main(download_many)

有两种方法能避免阻塞型调用中止整个应用程序的进程:
在单独的线程中运行各个阻塞型操作
把每个阻塞型操作转换成非阻塞的异步调用使用

现在你应该能理解为什么 flags_asyncio.py 脚本的性能比 flags.py 脚本高 5 倍了:flags.py
脚本依序下载,而每次下载都要用几十亿个 CPU 周期等待结果。其实,CPU 同时做了很
多事,只是没有运行你的程序。与此相比,在 flags_asyncio.py 脚本中,在
download_many 函数中调用 loop.run_until_complete 方法时,事件循环驱动各个
download_one 协程,运行到第一个 yield from 表达式处,那个表达式又驱动各个
get_flag 协程,运行到第一个 yield from 表达式处,调用 aiohttp.request(...)
函数。这些调用都不会阻塞,因此在零点几秒内所有请求全部开始。
asyncio 的基础设施获得第一个响应后,事件循环把响应发给等待结果的 get_flag 协
程。得到响应后,get_flag 向前执行到下一个 yield from 表达式处,调用
resp.read() 方法,然后把控制权还给主循环。其他响应会陆续返回(因为请求几乎同
时发出)。所有 get_ flag 协程都获得结果后,委派生成器 download_one 恢复,保存
图像文件。

因为异步操作是交叉执行的,所以并发下载多张图像所需的总时间比依序下载少得多。我
使用 asyncio 包发起了 600 个 HTTP 请求,获得所有结果的时间比依序下载快 70 倍。

关于concurrent.future模块以及asyncio模块的内容不容易理解,需要查阅其他资料,另写一篇博文。

流畅的python第十八章使用asyncio包处理并发的更多相关文章

  1. 【Python】Java程序员学习Python(十)— 类、包和模块

    我觉得学习到现在应该得掌握Python的OOP编程了,但是现在还没有应用到,先留一个坑. 一.类和对象 说到类和对象其实就是在说面向对象编程,学完Java以后我觉得面向对象编程还是很不错的,首先封装了 ...

  2. 流畅python学习笔记第十八章:使用asyncio包处理并发(一)

    首先是线程与协程的对比.在文中作者通过一个实例分别采用线程实现和asynchio包实现来比较两者的差别.在多线程的样例中,会用到join的方法,下面来介绍下join方法的使用. 知识点一:当一个进程启 ...

  3. 流畅python学习笔记第十八章:使用asyncio包处理并发(二)

    前面介绍了asyncio的用法.下面我们来看下如何用协程的方式来实现之前的旋转指针的方法 @asyncio.coroutine def spin(msg): write,flush=sys.stdou ...

  4. 流畅的python第十九章元编程学习记录

    在 Python 中,数据的属性和处理数据的方法统称属性(attribute).其实,方法只是可调用的属性.除了这二者之外,我们还可以创建特性(property),在不改变类接口的前提下,使用存取方法 ...

  5. 流畅的python第十六章协程学习记录

    从句法上看,协程与生成器类似,都是定义体中包含 yield 关键字的函数.可是,在协程中,yield 通常出现在表达式的右边(例如,datum = yield),可以产出值,也可以不产出——如果 yi ...

  6. 流畅的python第十五章上下文管理器和else块学习记录

    with 语句和上下文管理器for.while 和 try 语句的 else 子句 with 语句会设置一个临时的上下文,交给上下文管理器对象控制,并且负责清理上下文.这么做能避免错误并减少样板代码, ...

  7. 流畅的python第十四章可迭代的对象,迭代器和生成器学习记录

    在python中,所有集合都可以迭代,在python语言内部,迭代器用于支持 for循环 构建和扩展集合类型 逐行遍历文本文件 列表推导,字典推导和集合推导 元组拆包 调用函数时,使用*拆包实参 本章 ...

  8. 流畅的python第十二章继承的优缺点学习记录

    子类化内置类型的缺点 多重集成和方法解析顺序 tkinter

  9. 流畅的python笔记

    鸭子类型协议不完全总结序列:len,getitem切片:getitemv[0]分量的取值和写值:getitem和setitemv.x属性的取值和写值:getattr和setattr迭代:1)iter, ...

随机推荐

  1. IEDA自动清除无用的import

    Before After Step     快捷键展示格式化对话框:ctrl + shift + alt + l   格式化快捷键:ctrl + alt + l <wiz_tmp_tag id= ...

  2. python之路——面向对象进阶

    阅读目录 isinstance和issubclass 反射 setattr delattr getattr hasattr __str__和__repr__ __del__ item系列 __geti ...

  3. Linux 基础——开山篇

    为什么要开始学习Linux命令? 首先当然是因为工作需要了,现在的工作是负责银行调度的系统的源系统接入的工作,经常要到生产部署版本.所以……买了一本<Linux命令行与shell脚本编程大全&g ...

  4. eclipse+opencv

    https://docs.opencv.org/2.4/doc/tutorials/introduction/linux_eclipse/linux_eclipse.html

  5. Java学习笔记(十二)——eclipse和SVN配置,导入SVN服务器项目

    [前面的话] 北京的天气外加自己的不小心终于病了,在病的过程中,感觉身体好着真好,可以学习,可以吃好吃的,可以去运动,这一病了,干什么都感觉没有力气,身体好着真好. 这个文章的背景是:领导把项目最开始 ...

  6. windows命令启动mysql

    找到mysql的安装位置,进入bin目录 dos输入  mysql -h localhost -uroot -p   ,在输入密码

  7. 在 Bootstraptable 插件基础上新增可编辑行

    http://www.tuicool.com/articles/YbEVv2v 为什么调用 bootstraptable 原生方法会有问题 首先我必须肯定, bootstraptable 是一款很强大 ...

  8. AngularJS自定义指令及指令配置项

    两种写法 //第一种 angular.module('MyApp',[]) .directive('zl1',zl1) .controller('con1',['$scope',func1]); fu ...

  9. python——聊聊iterable,sequence和iterators

    ---------------------------------------------------------------前言----------------------------------- ...

  10. 读书笔记(高性能javascript)(二)

    5. 字符串和正则表达式: (1) 在大多数浏览器中,数组项合并(Array.prototype.join)比其他字符串连接方法更慢,但它却在IE7及更早版本浏览器中合并大量字符串唯一高效的途径: ( ...