对比一个简单的多线程程序和对应的 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. 机器学习方法:回归(一):线性回归Linear regression

    欢迎转载,转载请注明:本文出自Bin的专栏blog.csdn.net/xbinworld. 开一个机器学习方法科普系列:做基础回顾之用,学而时习之:也拿出来与大家分享.数学水平有限,只求易懂,学习与工 ...

  2. Insertion Sort List——链表的插入排序

    Sort a linked list using insertion sort. 这道题跟 Sort List 类似,要求在链表上实现一种排序算法,这道题是指定实现插入排序.插入排序是一种O(n^2) ...

  3. 【hdoj_2082】找单词

    题目:http://acm.hdu.edu.cn/showproblem.php?pid=2082 此题采用母函数的知识求解,套用母函数模板即可: http://blog.csdn.NET/ten_s ...

  4. websocket小荔枝

    关于websocket原理和好处请参考百度,这里只是代码. var ws = new WebSocket('ws://echo.websocket.org/'); ws.onopen = functi ...

  5. vue-music 关于playlist (底部播放列表组件)

    建立playlist.vue 组件,在player.vue 组件中引用,点击迷你播放器的播放列表按钮由下至上弹出这个层,所以在player.vue 播放器组件中引用 在playlist.vue 组件中 ...

  6. 从零开始做SSH项目(一)

    1.数据库脚本 用户表 CREATE TABLE `ybl`.`userinfo`( `id` INT NOT NULL AUTO_INCREMENT, `email` ) NOT NULL, `id ...

  7. 洛谷P3195 [HNOI2008] 玩具装箱 [DP,斜率优化,单调队列优化]

    题目传送门 题目描述 P教授要去看奥运,但是他舍不下他的玩具,于是他决定把所有的玩具运到北京.他使用自己的压缩器进行压缩,其可以将任意物品变成一堆,再放到一种特殊的一维容器中.P教授有编号为1...N ...

  8. html不识别<br/>,后台返回<br/>,前端不换行解决办法

    今天编写页面,后台直接返回带有html格式的字符串,包含<br/>,前端以为要展示<br/>,将其解析为<br/>页面不换行 解决办法 后台将<br/> ...

  9. 洛谷——P1743 矩阵 III

    P1743 矩阵 III 题目背景 usqwedf 改编系列题. 题目描述 给定一个n*m的矩阵,问从左上角走到右下角有多少条路径. 输入输出格式 输入格式: 一行两个正整数 n,m 输出格式: 路径 ...

  10. Nginx日志统一格式

    统一格式如下:nginx.conf 纯文本: log_format main '$remote_addr - $remote_user [$time_local] "$request&quo ...