流畅的python第十八章使用asyncio包处理并发
对比一个简单的多线程程序和对应的 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包处理并发的更多相关文章
- 【Python】Java程序员学习Python(十)— 类、包和模块
我觉得学习到现在应该得掌握Python的OOP编程了,但是现在还没有应用到,先留一个坑. 一.类和对象 说到类和对象其实就是在说面向对象编程,学完Java以后我觉得面向对象编程还是很不错的,首先封装了 ...
- 流畅python学习笔记第十八章:使用asyncio包处理并发(一)
首先是线程与协程的对比.在文中作者通过一个实例分别采用线程实现和asynchio包实现来比较两者的差别.在多线程的样例中,会用到join的方法,下面来介绍下join方法的使用. 知识点一:当一个进程启 ...
- 流畅python学习笔记第十八章:使用asyncio包处理并发(二)
前面介绍了asyncio的用法.下面我们来看下如何用协程的方式来实现之前的旋转指针的方法 @asyncio.coroutine def spin(msg): write,flush=sys.stdou ...
- 流畅的python第十九章元编程学习记录
在 Python 中,数据的属性和处理数据的方法统称属性(attribute).其实,方法只是可调用的属性.除了这二者之外,我们还可以创建特性(property),在不改变类接口的前提下,使用存取方法 ...
- 流畅的python第十六章协程学习记录
从句法上看,协程与生成器类似,都是定义体中包含 yield 关键字的函数.可是,在协程中,yield 通常出现在表达式的右边(例如,datum = yield),可以产出值,也可以不产出——如果 yi ...
- 流畅的python第十五章上下文管理器和else块学习记录
with 语句和上下文管理器for.while 和 try 语句的 else 子句 with 语句会设置一个临时的上下文,交给上下文管理器对象控制,并且负责清理上下文.这么做能避免错误并减少样板代码, ...
- 流畅的python第十四章可迭代的对象,迭代器和生成器学习记录
在python中,所有集合都可以迭代,在python语言内部,迭代器用于支持 for循环 构建和扩展集合类型 逐行遍历文本文件 列表推导,字典推导和集合推导 元组拆包 调用函数时,使用*拆包实参 本章 ...
- 流畅的python第十二章继承的优缺点学习记录
子类化内置类型的缺点 多重集成和方法解析顺序 tkinter
- 流畅的python笔记
鸭子类型协议不完全总结序列:len,getitem切片:getitemv[0]分量的取值和写值:getitem和setitemv.x属性的取值和写值:getattr和setattr迭代:1)iter, ...
随机推荐
- hdu 1325(并查集)
Is It A Tree? Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Tot ...
- react-router 4.0 升级攻略
react-router 4.0 出来好9了,项目在4月份的时候对react-router进行了升级,升级耗费了3天,一个坑一个坑踩了过来. 按照公司项目情况说下升级改了哪些,项目使用的是hashHi ...
- head first (三):装饰者模式
看到别人写的,都看不进去,算了还是自己手写一遍吧,算是帮助自己理解了.写的比较简单,例子也比较好懂,什么时候使用自己看着办. 1.定义 装饰者模式:动态地将职责附加到对象上.若要扩展功能,装饰者提供比 ...
- 部署Centos7
挂载和导入镜像 mount /dev/cdrom /media ll /media/ cobbler import --path=/media --name=centos7.4 --arch=x86_ ...
- HP自动检查html标签是否闭合
function HtmlClose($body) { $strlen_var = strlen($body); // 不包含 html 标签 if (strpos($body, '<') == ...
- CodeVS1169 传纸条 [DP补完计划]
题目传送门 题目描述 Description 小渊和小轩是好朋友也是同班同学,他们在一起总有谈不完的话题.一次素质拓展活动中,班上同学安排做成一个m行n列的矩阵,而小渊和小轩被安排在矩阵对角线的两端, ...
- 读书笔记(javascript 高级程序设计)
一. 数据类型: 1. undefined: 未声明和未初始化的变量,typeof 操作符返回的结果都是 undefined:(建议未初始化的变量进行显式赋值,这样当 typeof 返回 undefi ...
- JavaScript函数的防抖和节流
防抖 触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间 思路: 每次触发事件时都取消之前的延时调用方法 function debounce(fn) { let tim ...
- SCU 4444 Travel (补图最短路)
Travel The country frog lives in has \(n\) towns which are conveniently numbered by \(1, 2, \dots, n ...
- apue第16章笔记
intel 都是小端,小端即最低有效字节在最低地址上. tcp/ip协议栈使用大端字节序. connect失败可能是一瞬时的,用指数补偿算法处理,exponential backoff.但是在bsd套 ...